<commit_before>package shortnames

import (
	"fmt"
	"os"
	"strings"

	"github.com/containers/image/v5/docker/reference"
	"github.com/containers/image/v5/pkg/sysregistriesv2"
	"github.com/containers/image/v5/types"
	"github.com/manifoldco/promptui"
	"github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh/terminal"
)

// IsShortName returns true if the specified input is a "short name".  A "short
// name" refers to a container image without a fully-qualified reference, and
// is hence missing a registry (or domain).  Names including a digest are not
// short names.
//
// Examples:
//  * short names: "image:tag", "library/fedora"
//  * not short names: "quay.io/image", "localhost/image:tag",
//                     "server.org:5000/lib/image", "image@sha256:..."
func IsShortName(input string) bool {
	isShort, _, _ := parseUnnormalizedShortName(input)
	return isShort
}

// parseUnnormalizedShortName parses the input and returns if it's short name,
// the unnormalized reference.Named, and a parsing error.
func parseUnnormalizedShortName(input string) (bool, reference.Named, error) {
	ref, err := reference.Parse(input)
	if err != nil {
		return false, nil, errors.Wrapf(err, "cannot parse input: %q", input)
	}

	named, ok := ref.(reference.Named)
	if !ok {
		return true, nil, errors.Errorf("%q is not a named reference", input)
	}

	registry := reference.Domain(named)
	if strings.ContainsAny(registry, ".:") || registry == "localhost" {
		// A final parse to make sure that docker.io references are correctly
		// normalized (e.g., docker.io/alpine to docker.io/library/alpine.
		named, err = reference.ParseNormalizedNamed(input)
		if err != nil {
			return false, nil, errors.Wrapf(err, "cannot normalize input: %q", input)
		}
		return false, named, nil
	}

	return true, named, nil
}

// splitUserInput parses the user-specified reference.  Namely, it strips off
// the tag or digest and stores it in the return values so that both can be
// re-added to a possible resolved alias' or USRs at a later point.
func splitUserInput(named reference.Named) (isTagged bool, isDigested bool, normalized reference.Named, tag string, digest digest.Digest) {
	normalized = named

	tagged, isT := named.(reference.NamedTagged)
	if isT {
		isTagged = true
		tag = tagged.Tag()
	}

	digested, isD := named.(reference.Digested)
	if isD {
		isDigested = true
		digest = digested.Digest()
	}

	// Strip off tag/digest if present.
	normalized = reference.TrimNamed(named)

	return
}

// Add records the specified name-value pair as a new short-name alias to the
// user-specific aliases.conf.  It may override an existing alias for `name`.
func Add(ctx *types.SystemContext, name string, value reference.Named) error {
	isShort, _, err := parseUnnormalizedShortName(name)
	if err != nil {
		return err
	}
	if !isShort {
		return errors.Errorf("%q is not a short name", name)
	}
	return sysregistriesv2.AddShortNameAlias(ctx, name, value.String())
}

// Remove clears the short-name alias for the specified name.  It throws an
// error in case name does not exist in the machine-generated
// short-name-alias.conf.  In such case, the alias must be specified in one of
// the registries.conf files, which is the users' responsibility.
func Remove(ctx *types.SystemContext, name string) error {
	isShort, _, err := parseUnnormalizedShortName(name)
	if err != nil {
		return err
	}
	if !isShort {
		return errors.Errorf("%q is not a short name", name)
	}
	return sysregistriesv2.RemoveShortNameAlias(ctx, name)
}

// Resolved encapsulates all data for a resolved image name.
type Resolved struct {
	PullCandidates []PullCandidate

	userInput         reference.Named
	systemContext     *types.SystemContext
	rationale         rationale
	originDescription string
}

func (r *Resolved) addCandidate(named reference.Named) {
	r.PullCandidates = append(r.PullCandidates, PullCandidate{named, false, r})
}

func (r *Resolved) addCandidateToRecord(named reference.Named) {
	r.PullCandidates = append(r.PullCandidates, PullCandidate{named, true, r})
}

// Allows to reason over pull errors and add some context information.
// Used in (*Resolved).WrapPullError.
type rationale int

const (
	// No additional context.
	rationaleNone rationale = iota
	// Resolved value is a short-name alias.
	rationaleAlias
	// Resolved value has been completed with an Unqualified Search Registry.
	rationaleUSR
	// Resolved value has been selected by the user (via the prompt).
	rationaleUserSelection
)

// Description returns a human-readable description about the resolution
// process (e.g., short-name alias, unqualified-search registries, etc.).
// It is meant to be printed before attempting to pull the pull candidates
// to make the short-name resolution more transparent to user.
//
// If the returned string is empty, it is not meant to be printed.
func (r *Resolved) Description() string {
	switch r.rationale {
	case rationaleAlias:
		return fmt.Sprintf("Resolved short name %q to a recorded short-name alias (origin: %s)", r.userInput, r.originDescription)
	case rationaleUSR:
		return fmt.Sprintf("Completed short name %q with unqualified-search registries (origin: %s)", r.userInput, r.originDescription)
	case rationaleUserSelection, rationaleNone:
		fallthrough
	default:
		return ""
	}
}

// FormatPullErrors is a convenience function to format errors that occurred<commit_msg>Cleanup description of shortname expansion<commit_after>package shortnames

import (
	"fmt"
	"os"
	"strings"

	"github.com/containers/image/v5/docker/reference"
	"github.com/containers/image/v5/pkg/sysregistriesv2"
	"github.com/containers/image/v5/types"
	"github.com/manifoldco/promptui"
	"github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh/terminal"
)

// IsShortName returns true if the specified input is a "short name".  A "short
// name" refers to a container image without a fully-qualified reference, and
// is hence missing a registry (or domain).  Names including a digest are not
// short names.
//
// Examples:
//  * short names: "image:tag", "library/fedora"
//  * not short names: "quay.io/image", "localhost/image:tag",
//                     "server.org:5000/lib/image", "image@sha256:..."
func IsShortName(input string) bool {
	isShort, _, _ := parseUnnormalizedShortName(input)
	return isShort
}

// parseUnnormalizedShortName parses the input and returns if it's short name,
// the unnormalized reference.Named, and a parsing error.
func parseUnnormalizedShortName(input string) (bool, reference.Named, error) {
	ref, err := reference.Parse(input)
	if err != nil {
		return false, nil, errors.Wrapf(err, "cannot parse input: %q", input)
	}

	named, ok := ref.(reference.Named)
	if !ok {
		return true, nil, errors.Errorf("%q is not a named reference", input)
	}

	registry := reference.Domain(named)
	if strings.ContainsAny(registry, ".:") || registry == "localhost" {
		// A final parse to make sure that docker.io references are correctly
		// normalized (e.g., docker.io/alpine to docker.io/library/alpine.
		named, err = reference.ParseNormalizedNamed(input)
		if err != nil {
			return false, nil, errors.Wrapf(err, "cannot normalize input: %q", input)
		}
		return false, named, nil
	}

	return true, named, nil
}

// splitUserInput parses the user-specified reference.  Namely, it strips off
// the tag or digest and stores it in the return values so that both can be
// re-added to a possible resolved alias' or USRs at a later point.
func splitUserInput(named reference.Named) (isTagged bool, isDigested bool, normalized reference.Named, tag string, digest digest.Digest) {
	normalized = named

	tagged, isT := named.(reference.NamedTagged)
	if isT {
		isTagged = true
		tag = tagged.Tag()
	}

	digested, isD := named.(reference.Digested)
	if isD {
		isDigested = true
		digest = digested.Digest()
	}

	// Strip off tag/digest if present.
	normalized = reference.TrimNamed(named)

	return
}

// Add records the specified name-value pair as a new short-name alias to the
// user-specific aliases.conf.  It may override an existing alias for `name`.
func Add(ctx *types.SystemContext, name string, value reference.Named) error {
	isShort, _, err := parseUnnormalizedShortName(name)
	if err != nil {
		return err
	}
	if !isShort {
		return errors.Errorf("%q is not a short name", name)
	}
	return sysregistriesv2.AddShortNameAlias(ctx, name, value.String())
}

// Remove clears the short-name alias for the specified name.  It throws an
// error in case name does not exist in the machine-generated
// short-name-alias.conf.  In such case, the alias must be specified in one of
// the registries.conf files, which is the users' responsibility.
func Remove(ctx *types.SystemContext, name string) error {
	isShort, _, err := parseUnnormalizedShortName(name)
	if err != nil {
		return err
	}
	if !isShort {
		return errors.Errorf("%q is not a short name", name)
	}
	return sysregistriesv2.RemoveShortNameAlias(ctx, name)
}

// Resolved encapsulates all data for a resolved image name.
type Resolved struct {
	PullCandidates []PullCandidate

	userInput         reference.Named
	systemContext     *types.SystemContext
	rationale         rationale
	originDescription string
}

func (r *Resolved) addCandidate(named reference.Named) {
	r.PullCandidates = append(r.PullCandidates, PullCandidate{named, false, r})
}

func (r *Resolved) addCandidateToRecord(named reference.Named) {
	r.PullCandidates = append(r.PullCandidates, PullCandidate{named, true, r})
}

// Allows to reason over pull errors and add some context information.
// Used in (*Resolved).WrapPullError.
type rationale int

const (
	// No additional context.
	rationaleNone rationale = iota
	// Resolved value is a short-name alias.
	rationaleAlias
	// Resolved value has been completed with an Unqualified Search Registry.
	rationaleUSR
	// Resolved value has been selected by the user (via the prompt).
	rationaleUserSelection
)

// Description returns a human-readable description about the resolution
// process (e.g., short-name alias, unqualified-search registries, etc.).
// It is meant to be printed before attempting to pull the pull candidates
// to make the short-name resolution more transparent to user.
//
// If the returned string is empty, it is not meant to be printed.
func (r *Resolved) Description() string {
	switch r.rationale {
	case rationaleAlias:
		return fmt.Sprintf("Resolved %q as an alias (%s)", r.userInput, r.originDescription)
	case rationaleUSR:
		return fmt.Sprintf("Resolving %q using unqualified-search registries (%s)", r.userInput, r.originDescription)
	case rationaleUserSelection, rationaleNone:
		fallthrough
	default:
		return ""
	}
}

// FormatPullErrors is a convenience function to format errors that occurred
{'fraction_non_alphanumeric': 0.10422273781902552, 'fraction_numerical': 0.0024129930394431553, 'mean_word_length': 4.016759776536313, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1755164', 'n_tokens_mistral': 3424, 'n_tokens_neox': 3235, 'n_words': 1137}