# Copyright 1999-2025 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import argparse
import logging
import locale
import platform
import shlex
import stat
import sys

import portage

from portage import os
from portage.repository.config import _find_bad_atoms
from portage.sync import _SUBMODULE_PATH_MAP

from typing import Optional

options = [
    "--alphabetical",
    "--ask-enter-invalid",
    "--buildpkgonly",
    "--changed-use",
    "--columns",
    "--debug",
    "--digest",
    "--emptytree",
    "--verbose-conflicts",
    "--fetchonly",
    "--fetch-all-uri",
    "--ignore-default-opts",
    "--noconfmem",
    "--newrepo",
    "--newuse",
    "--nobindeps",
    "--nodeps",
    "--noreplace",
    "--nospinner",
    "--oneshot",
    "--onlydeps",
    "--pretend",
    "--quiet-repo-display",
    "--quiet-unmerge-warn",
    "--resume",
    "--searchdesc",
    "--skipfirst",
    "--tree",
    "--unordered-display",
    "--update",
    "--update-if-installed",
]

shortmapping = {
    "1": "--oneshot",
    "B": "--buildpkgonly",
    "c": "--depclean",
    "C": "--unmerge",
    "d": "--debug",
    "e": "--emptytree",
    "f": "--fetchonly",
    "F": "--fetch-all-uri",
    "h": "--help",
    "n": "--noreplace",
    "N": "--newuse",
    "o": "--onlydeps",
    "O": "--nodeps",
    "p": "--pretend",
    "P": "--prune",
    "r": "--resume",
    "s": "--search",
    "S": "--searchdesc",
    "t": "--tree",
    "u": "--update",
    "U": "--changed-use",
    "V": "--version",
}

COWSAY_MOO = r"""

  Larry loves Gentoo ({})

 _______________________
< Have you mooed today? >
 -----------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

"""


def multiple_actions(action1, action2):
    sys.stderr.write("\n!!! Multiple actions requested... Please choose one only.\n")
    sys.stderr.write(f"!!! '{action1}' or '{action2}'\n\n")
    sys.exit(1)


def insert_optional_args(args):
    """
    Parse optional arguments and insert a value if one has
    not been provided. This is done before feeding the args
    to the optparse parser since that parser does not support
    this feature natively.
    """

    class valid_integers:
        def __contains__(self, s):
            try:
                return int(s) >= 0
            except (ValueError, OverflowError):
                return False

    valid_integers = valid_integers()

    class valid_floats:
        def __contains__(self, s):
            try:
                return float(s) >= 0
            except (ValueError, OverflowError):
                return False

    valid_floats = valid_floats()

    y_or_n = (
        "y",
        "n",
    )

    class valid_integers_or_y_or_n:
        def __contains__(self, s):
            if s in valid_integers:
                return True
            return s in y_or_n

    valid_integers_or_y_or_n = valid_integers_or_y_or_n()

    new_args = []

    default_arg_opts = {
        "--alert": y_or_n,
        "--ask": y_or_n,
        "--autounmask": y_or_n,
        "--autounmask-continue": y_or_n,
        "--autounmask-only": y_or_n,
        "--autounmask-keep-keywords": y_or_n,
        "--autounmask-keep-masks": y_or_n,
        "--autounmask-unrestricted-atoms": y_or_n,
        "--autounmask-write": y_or_n,
        "--binpkg-changed-deps": y_or_n,
        "--buildpkg": y_or_n,
        "--changed-deps": y_or_n,
        "--changed-slot": y_or_n,
        "--changed-deps-report": y_or_n,
        "--complete-graph": y_or_n,
        "--deep": valid_integers,
        "--depclean-lib-check": y_or_n,
        "--deselect": y_or_n,
        "--binpkg-respect-use": y_or_n,
        "--fail-clean": y_or_n,
        "--fuzzy-search": y_or_n,
        "--getbinpkg": y_or_n,
        "--getbinpkgonly": y_or_n,
        "--ignore-world": y_or_n,
        "--jobs": valid_integers_or_y_or_n,
        "--jobs-tmpdir-require-free-gb": valid_integers,
        "--keep-going": y_or_n,
        "--load-average": valid_floats,
        "--onlydeps-with-ideps": y_or_n,
        "--onlydeps-with-rdeps": y_or_n,
        "--package-moves": y_or_n,
        "--quiet": y_or_n,
        "--quiet-build": y_or_n,
        "--quiet-fail": y_or_n,
        "--read-news": y_or_n,
        "--rebuild-if-new-slot": y_or_n,
        "--rebuild-if-new-rev": y_or_n,
        "--rebuild-if-new-ver": y_or_n,
        "--rebuild-if-unbuilt": y_or_n,
        "--rebuilt-binaries": y_or_n,
        "--root-deps": ("rdeps",),
        "--select": y_or_n,
        "--selective": y_or_n,
        "--use-ebuild-visibility": y_or_n,
        "--usepkg": y_or_n,
        "--usepkgonly": y_or_n,
        "--usepkg-exclude-live": y_or_n,
        "--verbose": y_or_n,
        "--verbose-missing-ebuilds": y_or_n,
        "--verbose-slot-rebuilds": y_or_n,
        "--with-test-deps": y_or_n,
    }

    short_arg_opts = {
        "D": valid_integers,
        "j": valid_integers,
        "l": valid_floats,
    }

    # Don't make things like "-kn" expand to "-k n"
    # since existence of -n makes it too ambiguous.
    short_arg_opts_n = {
        "a": y_or_n,
        "A": y_or_n,
        "b": y_or_n,
        "g": y_or_n,
        "G": y_or_n,
        "k": y_or_n,
        "K": y_or_n,
        "q": y_or_n,
        "v": y_or_n,
        "w": y_or_n,
        "W": y_or_n,
    }

    arg_stack = args[:]
    arg_stack.reverse()
    while arg_stack:
        arg = arg_stack.pop()

        default_arg_choices = default_arg_opts.get(arg)
        if default_arg_choices is not None:
            new_args.append(arg)
            if arg_stack and arg_stack[-1] in default_arg_choices:
                new_args.append(arg_stack.pop())
            else:
                # insert default argument
                new_args.append("True")
            continue

        if arg[:1] != "-" or arg[:2] == "--":
            new_args.append(arg)
            continue

        match = None
        for k, arg_choices in short_arg_opts.items():
            if k in arg:
                match = k
                break

        if match is None:
            for k, arg_choices in short_arg_opts_n.items():
                if k in arg:
                    match = k
                    break

        if match is None:
            new_args.append(arg)
            continue

        if len(arg) == 2:
            new_args.append(arg)
            if arg_stack and arg_stack[-1] in arg_choices:
                new_args.append(arg_stack.pop())
            else:
                # insert default argument
                new_args.append("True")
            continue

        # Insert an empty placeholder in order to
        # satisfy the requirements of optparse.

        new_args.append("-" + match)
        opt_arg = None
        saved_opts = None

        if arg[1:2] == match:
            if match not in short_arg_opts_n and arg[2:] in arg_choices:
                opt_arg = arg[2:]
            else:
                saved_opts = arg[2:]
                opt_arg = "True"
        else:
            saved_opts = arg[1:].replace(match, "")
            opt_arg = "True"

        if opt_arg is None and arg_stack and arg_stack[-1] in arg_choices:
            opt_arg = arg_stack.pop()

        if opt_arg is None:
            new_args.append("True")
        else:
            new_args.append(opt_arg)

        if saved_opts is not None:
            # Recycle these on arg_stack since they
            # might contain another match.
            arg_stack.append("-" + saved_opts)

    return new_args


def parse_opts(tmpcmdline, silent=False):
    myaction = None
    myopts = {}

    actions = frozenset(
        [
            "clean",
            "check-news",
            "config",
            "depclean",
            "help",
            "info",
            "list-sets",
            "metadata",
            "moo",
            "prune",
            "rage-clean",
            "regen",
            "search",
            "sync",
            "unmerge",
            "version",
        ]
    )

    longopt_aliases = {"--cols": "--columns", "--skip-first": "--skipfirst"}
    y_or_n = ("y", "n")
    true_y_or_n = ("True", "y", "n")
    true_y = ("True", "y")
    argument_options = {
        "--alert": {
            "shortopt": "-A",
            "help": "alert (terminal bell) on prompts",
            "choices": true_y_or_n,
        },
        "--ask": {
            "shortopt": "-a",
            "help": "prompt before performing any actions",
            "choices": true_y_or_n,
        },
        "--autounmask": {
            "help": "automatically unmask packages",
            "choices": true_y_or_n,
        },
        "--autounmask-backtrack": {
            "help": (
                "continue backtracking when there are autounmask "
                "configuration changes"
            ),
            "choices": ("y", "n"),
        },
        "--autounmask-continue": {
            "help": "write autounmask changes and continue",
            "choices": true_y_or_n,
        },
        "--autounmask-only": {
            "help": "only perform --autounmask",
            "choices": true_y_or_n,
        },
        "--autounmask-license": {
            "help": "allow autounmask to change package.license",
            "choices": y_or_n,
        },
        "--autounmask-unrestricted-atoms": {
            "help": "write autounmask changes with >= atoms if possible",
            "choices": true_y_or_n,
        },
        "--autounmask-use": {
            "help": "allow autounmask to change package.use",
            "choices": y_or_n,
        },
        "--autounmask-keep-keywords": {
            "help": "don't add package.accept_keywords entries",
            "choices": true_y_or_n,
        },
        "--autounmask-keep-masks": {
            "help": "don't add package.unmask entries",
            "choices": true_y_or_n,
        },
        "--autounmask-write": {
            "help": "write changes made by --autounmask to disk",
            "choices": true_y_or_n,
        },
        "--accept-properties": {
            "help": "temporarily override ACCEPT_PROPERTIES",
            "action": "store",
        },
        "--accept-restrict": {
            "help": "temporarily override ACCEPT_RESTRICT",
            "action": "store",
        },
        "--backtrack": {
            "help": "Specifies how many times to backtrack if dependency "
            + "calculation fails ",
            "action": "store",
        },
        "--binpkg-changed-deps": {
            "help": ("reject binary packages with outdated " "dependencies"),
            "choices": true_y_or_n,
        },
        "--buildpkg": {
            "shortopt": "-b",
            "help": "build binary packages",
            "choices": true_y_or_n,
        },
        "--buildpkg-exclude": {
            "help": "A space separated list of package atoms for which "
            + "no binary packages should be built. This option overrides all "
            + "possible ways to enable building of binary packages.",
            "action": "append",
        },
        "--changed-deps": {
            "help": ("replace installed packages with " "outdated dependencies"),
            "choices": true_y_or_n,
        },
        "--changed-deps-report": {
            "help": ("report installed packages with " "outdated dependencies"),
            "choices": true_y_or_n,
        },
        "--changed-slot": {
            "help": ("replace installed packages with " "outdated SLOT metadata"),
            "choices": true_y_or_n,
        },
        "--config-root": {
            "help": "specify the location for portage configuration files",
            "action": "store",
        },
        "--color": {"help": "enable or disable color output", "choices": ("y", "n")},
        "--complete-graph": {
            "help": "completely account for all known dependencies",
            "choices": true_y_or_n,
        },
        "--complete-graph-if-new-use": {
            "help": "trigger --complete-graph behavior if USE or IUSE will change for an installed package",
            "choices": y_or_n,
        },
        "--complete-graph-if-new-ver": {
            "help": "trigger --complete-graph behavior if an installed package version will change (upgrade or downgrade)",
            "choices": y_or_n,
        },
        "--deep": {
            "shortopt": "-D",
            "help": "Specifies how deep to recurse into dependencies "
            + "of packages given as arguments. If no argument is given, "
            + "depth is unlimited. Default behavior is to skip "
            + "dependencies of installed packages.",
            "action": "store",
        },
        "--depclean-lib-check": {
            "help": "check for consumers of libraries before removing them",
            "choices": true_y_or_n,
        },
        "--deselect": {
            "shortopt": "-W",
            "help": "remove atoms/sets from the world file",
            "choices": true_y_or_n,
        },
        "--dynamic-deps": {
            "help": "substitute the dependencies of installed packages with the dependencies of unbuilt ebuilds",
            "choices": y_or_n,
        },
        "--exclude": {
            "shortopt": "-X",
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge won't  install any ebuild or binary package that "
            + "matches any of the given package atoms.",
            "action": "append",
        },
        "--fail-clean": {
            "help": "clean temp files after build failure",
            "choices": true_y_or_n,
        },
        "--fuzzy-search": {
            "help": "Enable or disable fuzzy search",
            "choices": true_y_or_n,
        },
        "--ignore-built-slot-operator-deps": {
            "help": "Ignore the slot/sub-slot := operator parts of dependencies that have "
            "been recorded when packages where built. This option is intended "
            "only for debugging purposes, and it only affects built packages "
            "that specify slot/sub-slot := operator dependencies.",
            "choices": y_or_n,
        },
        "--ignore-soname-deps": {
            "help": "Ignore the soname dependencies of binary and "
            "installed packages. This option is enabled by "
            "default, since soname dependencies are relatively "
            "new, and the required metadata is not guaranteed to "
            "exist for binary and installed packages built with "
            "older versions of portage.",
            "choices": y_or_n,
        },
        "--ignore-world": {
            "help": "ignore the @world package set and its dependencies",
            "choices": true_y_or_n,
        },
        "--implicit-system-deps": {
            "help": "Assume that packages may have implicit dependencies on"
            "packages which belong to the @system set",
            "choices": y_or_n,
        },
        "--jobs": {
            "shortopt": "-j",
            "help": "Specifies the number of packages to build " + "simultaneously.",
            "action": "store",
        },
        "--jobs-tmpdir-require-free-gb": {
            "help": "Specifies the required remaining capacity (in GiB) of PORTAGE_TMPDIR before a new emerge job is started. Set to 0 to disable this check",
            "action": "store",
        },
        "--keep-going": {
            "help": "continue as much as possible after an error",
            "choices": true_y_or_n,
        },
        "--load-average": {
            "shortopt": "-l",
            "help": "Specifies that no new builds should be started "
            + "if there are other builds running and the load average "
            + "is at least LOAD (a floating-point number).",
            "action": "store",
        },
        "--misspell-suggestions": {
            "help": "enable package name misspell suggestions",
            "choices": ("y", "n"),
        },
        "--with-bdeps": {
            "help": "include unnecessary build time dependencies",
            "choices": ("y", "n"),
        },
        "--with-bdeps-auto": {
            "help": (
                "automatically enable --with-bdeps for installation"
                " actions, unless --usepkg is enabled"
            ),
            "choices": ("y", "n"),
        },
        "--reinstall": {
            "help": "specify conditions to trigger package reinstallation",
            "choices": ["changed-use"],
        },
        "--reinstall-atoms": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will treat matching packages as if they are not "
            + "installed, and reinstall them if necessary. Implies --deep.",
            "action": "append",
        },
        "--binpkg-respect-use": {
            "help": "discard binary packages if their use flags \
				don't match the current configuration",
            "choices": true_y_or_n,
        },
        "--getbinpkg": {
            "shortopt": "-g",
            "help": "fetch binary packages",
            "choices": true_y_or_n,
        },
        "--getbinpkgonly": {
            "shortopt": "-G",
            "help": "fetch binary packages only",
            "choices": true_y_or_n,
        },
        "--getbinpkg-exclude": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will not fetch matching remote binary packages. ",
            "action": "append",
        },
        "--getbinpkg-include": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will not fetch non-matching remote binary packages. ",
            "action": "append",
        },
        "--usepkg-exclude": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will ignore matching binary packages. ",
            "action": "append",
        },
        "--usepkg-include": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will ignore non-matching binary packages. ",
            "action": "append",
        },
        "--onlydeps-with-ideps": {
            "help": "modify interpretation of dependencies to include IDEPEND",
            "choices": true_y_or_n,
        },
        "--onlydeps-with-rdeps": {
            "help": "modify interpretation of dependencies",
            "choices": true_y_or_n,
        },
        "--rebuild-exclude": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will not rebuild these packages due to the "
            + "--rebuild flag. ",
            "action": "append",
        },
        "--rebuild-ignore": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will not rebuild packages that depend on matching "
            + "packages due to the --rebuild flag. ",
            "action": "append",
        },
        "--package-moves": {
            "help": "perform package moves when necessary",
            "choices": true_y_or_n,
        },
        "--prefix": {"help": "specify the installation prefix", "action": "store"},
        "--pkg-format": {
            "help": "format of result binary package",
            "action": "store",
        },
        "--quickpkg-direct": {
            "help": "Enable use of installed packages directly as binary packages",
            "choices": y_or_n,
        },
        "--quickpkg-direct-root": {
            "help": "Specify the root to use as the --quickpkg-direct package source",
            "action": "store",
        },
        "--quiet": {
            "shortopt": "-q",
            "help": "reduced or condensed output",
            "choices": true_y_or_n,
        },
        "--quiet-build": {
            "help": "redirect build output to logs",
            "choices": true_y_or_n,
        },
        "--quiet-fail": {
            "help": "suppresses display of the build log on stdout",
            "choices": true_y_or_n,
        },
        "--read-news": {
            "help": "offer to read unread news via eselect",
            "choices": true_y_or_n,
        },
        "--rebuild-if-new-slot": {
            "help": (
                "Automatically rebuild or reinstall packages when slot/sub-slot := "
                "operator dependencies can be satisfied by a newer slot, so that "
                "older packages slots will become eligible for removal by the "
                "--depclean action as soon as possible."
            ),
            "choices": true_y_or_n,
        },
        "--rebuild-if-new-rev": {
            "help": "Rebuild packages when dependencies that are "
            + "used at both build-time and run-time are built, "
            + "if the dependency is not already installed with the "
            + "same version and revision.",
            "choices": true_y_or_n,
        },
        "--rebuild-if-new-ver": {
            "help": "Rebuild packages when dependencies that are "
            + "used at both build-time and run-time are built, "
            + "if the dependency is not already installed with the "
            + "same version. Revision numbers are ignored.",
            "choices": true_y_or_n,
        },
        "--rebuild-if-unbuilt": {
            "help": "Rebuild packages when dependencies that are "
            + "used at both build-time and run-time are built.",
            "choices": true_y_or_n,
        },
        "--rebuilt-binaries": {
            "help": "replace installed packages with binary "
            + "packages that have been rebuilt",
            "choices": true_y_or_n,
        },
        "--rebuilt-binaries-timestamp": {
            "help": "use only binaries that are newer than this "
            + "timestamp for --rebuilt-binaries",
            "action": "store",
        },
        "--regex-search-auto": {
            "help": "Enable or disable automatic regular expression detection for search actions",
            "choices": y_or_n,
            "default": "y",
        },
        "--root": {
            "help": "specify the target root filesystem for merging packages",
            "action": "store",
        },
        "--root-deps": {
            "help": "modify interpretation of dependencies",
            "choices": ("True", "rdeps"),
        },
        "--search-index": {
            "help": "Enable or disable indexed search (enabled by default)",
            "choices": y_or_n,
        },
        "--search-similarity": {
            "help": (
                "Set minimum similarity percentage for fuzzy seach "
                "(a floating-point number between 0 and 100)"
            ),
            "action": "store",
        },
        "--select": {
            "shortopt": "-w",
            "help": "add specified packages to the world set "
            + "(inverse of --oneshot)",
            "choices": true_y_or_n,
        },
        "--selective": {"help": "identical to --noreplace", "choices": true_y_or_n},
        "--sync-submodule": {
            "help": (
                "Restrict sync to the specified submodule(s)." " (--sync action only)"
            ),
            "choices": tuple(_SUBMODULE_PATH_MAP),
            "action": "append",
        },
        "--sysroot": {
            "help": "specify the location for build dependencies specified in DEPEND",
            "action": "store",
        },
        "--use-ebuild-visibility": {
            "help": "use unbuilt ebuild metadata for visibility checks on built packages",
            "choices": true_y_or_n,
        },
        "--useoldpkg-atoms": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will prefer matching binary packages over newer unbuilt packages. ",
            "action": "append",
        },
        "--usepkg": {
            "shortopt": "-k",
            "help": "use binary packages",
            "choices": true_y_or_n,
        },
        "--usepkgonly": {
            "shortopt": "-K",
            "help": "use only binary packages",
            "choices": true_y_or_n,
        },
        "--usepkg-exclude-live": {
            "help": "do not install from binary packages for live ebuilds",
            "choices": true_y_or_n,
        },
        "--verbose": {
            "shortopt": "-v",
            "help": "verbose output",
            "choices": true_y_or_n,
        },
        "--verbose-missing-ebuilds": {
            "help": "verbose missing ebuild output",
            "choices": true_y_or_n,
        },
        "--verbose-slot-rebuilds": {
            "help": "verbose slot rebuild output",
            "choices": true_y_or_n,
        },
        "--with-test-deps": {
            "help": "pull in test deps for packages " + "matched by arguments",
            "choices": true_y_or_n,
        },
    }

    parser = argparse.ArgumentParser(add_help=False)

    for action_opt in actions:
        parser.add_argument(
            f"--{action_opt}",
            action="store_true",
            dest=action_opt.replace("-", "_"),
            default=False,
        )
    for myopt in options:
        parser.add_argument(
            myopt,
            action="store_true",
            dest=myopt.lstrip("--").replace("-", "_"),
            default=False,
        )
    for shortopt, longopt in shortmapping.items():
        parser.add_argument(
            f"-{shortopt}",
            action="store_true",
            dest=longopt.lstrip("--").replace("-", "_"),
            default=False,
        )
    for myalias, myopt in longopt_aliases.items():
        parser.add_argument(
            myalias,
            action="store_true",
            dest=myopt.lstrip("--").replace("-", "_"),
            default=False,
        )

    for myopt, kwargs in argument_options.items():
        shortopt = kwargs.pop("shortopt", None)
        args = [myopt]
        if shortopt is not None:
            args.append(shortopt)
        parser.add_argument(dest=myopt.lstrip("--").replace("-", "_"), *args, **kwargs)

    parser.add_argument("positional_args", nargs="*")

    tmpcmdline = insert_optional_args(tmpcmdline)

    myoptions = getattr(parser, "parse_intermixed_args", parser.parse_args)(
        args=tmpcmdline
    )

    if myoptions.alert in true_y:
        myoptions.alert = True
    else:
        myoptions.alert = None

    if myoptions.ask in true_y:
        myoptions.ask = True
    else:
        myoptions.ask = None

    if myoptions.autounmask in true_y:
        myoptions.autounmask = True

    if myoptions.autounmask_continue in true_y:
        myoptions.autounmask_continue = True

    if myoptions.autounmask_only in true_y:
        myoptions.autounmask_only = True
    else:
        myoptions.autounmask_only = None

    if myoptions.autounmask_unrestricted_atoms in true_y:
        myoptions.autounmask_unrestricted_atoms = True

    if myoptions.autounmask_keep_keywords in true_y:
        myoptions.autounmask_keep_keywords = True

    if myoptions.autounmask_keep_masks in true_y:
        myoptions.autounmask_keep_masks = True

    if myoptions.autounmask_write in true_y:
        myoptions.autounmask_write = True

    if myoptions.binpkg_changed_deps is not None:
        if myoptions.binpkg_changed_deps in true_y:
            myoptions.binpkg_changed_deps = "y"
        else:
            myoptions.binpkg_changed_deps = "n"

    if myoptions.buildpkg in true_y:
        myoptions.buildpkg = True

    if myoptions.buildpkg_exclude:
        bad_atoms = _find_bad_atoms(myoptions.buildpkg_exclude, less_strict=True)
        if bad_atoms and not silent:
            invalid_atoms = ",".join(bad_atoms)
            parser.error(
                f"Invalid Atom(s) in --buildpkg-exclude parameter: '{invalid_atoms}'\n"
            )

    if myoptions.changed_deps is not None:
        if myoptions.changed_deps in true_y:
            myoptions.changed_deps = "y"
        else:
            myoptions.changed_deps = "n"

    if myoptions.changed_deps_report is not None:
        if myoptions.changed_deps_report in true_y:
            myoptions.changed_deps_report = "y"
        else:
            myoptions.changed_deps_report = "n"

    if myoptions.changed_slot is not None:
        if myoptions.changed_slot in true_y:
            myoptions.changed_slot = True
        else:
            myoptions.changed_slot = None

    if myoptions.changed_use is not False:
        myoptions.reinstall = "changed-use"
        myoptions.changed_use = False

    if myoptions.deselect in true_y:
        myoptions.deselect = True

    if myoptions.binpkg_respect_use is not None:
        if myoptions.binpkg_respect_use in true_y:
            myoptions.binpkg_respect_use = "y"
        else:
            myoptions.binpkg_respect_use = "n"

    if myoptions.complete_graph in true_y:
        myoptions.complete_graph = True
    else:
        myoptions.complete_graph = None

    if myoptions.depclean_lib_check in true_y:
        myoptions.depclean_lib_check = True

    candidate_bad_options = (
        (myoptions.exclude, "exclude"),
        (myoptions.getbinpkg_exclude, "getbinpkg-exclude"),
        (myoptions.getbinpkg_include, "getbinpkg-include"),
        (myoptions.reinstall_atoms, "reinstall-atoms"),
        (myoptions.rebuild_exclude, "rebuild-exclude"),
        (myoptions.rebuild_ignore, "rebuild-ignore"),
        (myoptions.usepkg_exclude, "usepkg-exclude"),
        (myoptions.usepkg_include, "usepkg-include"),
        (myoptions.useoldpkg_atoms, "useoldpkg-atoms"),
    )
    bad_options = (
        (_find_bad_atoms(atoms), flag) for atoms, flag in candidate_bad_options if atoms
    )

    for bad_atoms, flag in bad_options:
        if bad_atoms and not silent:
            invalid_atoms = ",".join(bad_atoms)
            parser.error(
                f"Invalid Atom(s) in --{flag} parameter: '{invalid_atoms}' (only package names and slot atoms (with wildcards) allowed)\n"
            )

    if myoptions.fail_clean in true_y:
        myoptions.fail_clean = True

    if myoptions.fuzzy_search in true_y:
        myoptions.fuzzy_search = True

    if myoptions.getbinpkg in true_y:
        myoptions.getbinpkg = True
    elif myoptions.getbinpkg == "n":
        myoptions.getbinpkg = False
    else:
        myoptions.getbinpkg = None

    if myoptions.getbinpkgonly in true_y:
        myoptions.getbinpkgonly = True
    elif myoptions.getbinpkgonly == "n":
        myoptions.getbinpkgonly = False
    else:
        myoptions.getbinpkgonly = None

    if myoptions.ignore_world in true_y:
        myoptions.ignore_world = True

    if myoptions.keep_going in true_y:
        myoptions.keep_going = True
    else:
        myoptions.keep_going = None

    if myoptions.package_moves in true_y:
        myoptions.package_moves = True

    if myoptions.quiet in true_y:
        myoptions.quiet = True
    else:
        myoptions.quiet = None

    if myoptions.quiet_build in true_y:
        myoptions.quiet_build = "y"

    if myoptions.quiet_fail in true_y:
        myoptions.quiet_fail = "y"

    if myoptions.read_news in true_y:
        myoptions.read_news = True
    else:
        myoptions.read_news = None

    if myoptions.rebuild_if_new_slot in true_y:
        myoptions.rebuild_if_new_slot = "y"

    if myoptions.rebuild_if_new_ver in true_y:
        myoptions.rebuild_if_new_ver = True
    else:
        myoptions.rebuild_if_new_ver = None

    if myoptions.rebuild_if_new_rev in true_y:
        myoptions.rebuild_if_new_rev = True
        myoptions.rebuild_if_new_ver = None
    else:
        myoptions.rebuild_if_new_rev = None

    if myoptions.rebuild_if_unbuilt in true_y:
        myoptions.rebuild_if_unbuilt = True
        myoptions.rebuild_if_new_rev = None
        myoptions.rebuild_if_new_ver = None
    else:
        myoptions.rebuild_if_unbuilt = None

    if myoptions.rebuilt_binaries in true_y:
        myoptions.rebuilt_binaries = True

    if myoptions.root_deps in true_y:
        myoptions.root_deps = True

    if myoptions.select in true_y:
        myoptions.select = True
        myoptions.oneshot = False
    elif myoptions.select == "n":
        myoptions.oneshot = True

    if myoptions.selective in true_y:
        myoptions.selective = True

    if myoptions.backtrack is not None:
        try:
            backtrack = int(myoptions.backtrack)
        except (OverflowError, ValueError):
            backtrack = -1

        if backtrack < 0:
            backtrack = None
            if not silent:
                parser.error(
                    f"Invalid --backtrack parameter: '{myoptions.backtrack}'\n"
                )

        myoptions.backtrack = backtrack

    if myoptions.deep is not None:
        deep = None
        if myoptions.deep == "True":
            deep = True
        else:
            try:
                deep = int(myoptions.deep)
            except (OverflowError, ValueError):
                deep = -1

        if deep is not True and deep < 0:
            deep = None
            if not silent:
                parser.error(f"Invalid --deep parameter: '{myoptions.deep}'\n")

        myoptions.deep = deep

    if myoptions.jobs is not None:
        jobs = None
        if myoptions.jobs in ("True", "y"):
            jobs = True
        elif myoptions.jobs == "n":
            jobs = None
        else:
            try:
                jobs = int(myoptions.jobs)
            except ValueError:
                if not silent:
                    parser.error(f"Invalid --jobs parameter: '{myoptions.jobs}'\n")

        if jobs == 0:
            from portage.util.cpuinfo import get_cpu_count

            jobs = get_cpu_count()

        myoptions.jobs = jobs

    if myoptions.jobs_tmpdir_require_free_gb:
        try:
            jobs_tmpdir_require_free_gb = int(myoptions.jobs_tmpdir_require_free_gb)
        except ValueError:
            jobs_tmpdir_require_free_gb = 0
            if not silent:
                parser.error(
                    f"Invalid --jobs-tmpdir-require-free-gb parameter: '{myoptions.jobs_tmpdir_require_free_gb}'\n"
                )

        myoptions.jobs_tmpdir_require_free_gb = jobs_tmpdir_require_free_gb

    if myoptions.load_average == "True":
        myoptions.load_average = None

    if myoptions.load_average:
        try:
            load_average = float(myoptions.load_average)
        except ValueError:
            load_average = 0.0

        if load_average <= 0.0:
            load_average = None
            if not silent:
                parser.error(
                    f"Invalid --load-average parameter: '{myoptions.load_average}'\n"
                )

        myoptions.load_average = load_average

    if myoptions.rebuilt_binaries_timestamp:
        try:
            rebuilt_binaries_timestamp = int(myoptions.rebuilt_binaries_timestamp)
        except ValueError:
            rebuilt_binaries_timestamp = -1

        if rebuilt_binaries_timestamp < 0:
            rebuilt_binaries_timestamp = 0
            if not silent:
                parser.error(
                    f"Invalid --rebuilt-binaries-timestamp parameter: '{myoptions.rebuilt_binaries_timestamp}'\n"
                )

        myoptions.rebuilt_binaries_timestamp = rebuilt_binaries_timestamp

    if myoptions.search_similarity:
        try:
            search_similarity = float(myoptions.search_similarity)
        except ValueError:
            parser.error(
                f"Invalid --search-similarity parameter (not a number): '{myoptions.search_similarity}'\n"
            )

        if search_similarity < 0 or search_similarity > 100:
            parser.error(
                f"Invalid --search-similarity parameter (not between 0 and 100): '{myoptions.search_similarity}'\n"
            )

        myoptions.search_similarity = search_similarity

    if myoptions.use_ebuild_visibility in true_y:
        myoptions.use_ebuild_visibility = True
    else:
        # None or "n"
        pass

    if myoptions.usepkg in true_y:
        myoptions.usepkg = True
    elif myoptions.usepkg == "n":
        myoptions.usepkg = False
    else:
        myoptions.usepkg = None

    if myoptions.usepkgonly in true_y:
        myoptions.usepkgonly = True
    elif myoptions.usepkgonly == "n":
        myoptions.usepkgonly = False
    else:
        myoptions.usepkgonly = None

    if myoptions.usepkg_exclude_live in true_y:
        myoptions.usepkg_exclude_live = True
    else:
        myoptions.usepkg_exclude_live = None

    if myoptions.verbose in true_y:
        myoptions.verbose = True
    else:
        myoptions.verbose = None

    if myoptions.with_test_deps in true_y:
        myoptions.with_test_deps = True
    else:
        myoptions.with_test_deps = None

    for myopt in options:
        v = getattr(myoptions, myopt.lstrip("--").replace("-", "_"))
        if v:
            myopts[myopt] = True

    for myopt in argument_options:
        v = getattr(myoptions, myopt.lstrip("--").replace("-", "_"), None)
        if v is not None:
            myopts[myopt] = v

    if myoptions.searchdesc:
        myoptions.search = True

    for action_opt in actions:
        v = getattr(myoptions, action_opt.replace("-", "_"))
        if v:
            if myaction:
                multiple_actions(myaction, action_opt)
                sys.exit(1)
            myaction = action_opt

    if myaction is None and myoptions.deselect is True:
        myaction = "deselect"

    return myaction, myopts, myoptions.positional_args


def profile_check(trees, myaction):
    import textwrap
    from _emerge.actions import validate_ebuild_environment
    from portage.util import writemsg_level

    if myaction in ("help", "info", "search", "sync", "version"):
        return os.EX_OK
    for root_trees in trees.values():
        if (
            root_trees["root_config"].settings.profiles
            and "ARCH" in root_trees["root_config"].settings
        ):
            continue
        # generate some profile related warning messages
        validate_ebuild_environment(trees)
        msg = (
            "Your current profile is invalid. If you have just changed "
            "your profile configuration, you should revert back to the "
            "previous configuration. Allowed actions are limited to "
            "--help, --info, --search, --sync, and --version."
        )
        writemsg_level(
            "".join(f"!!! {l}\n" for l in textwrap.wrap(msg, 70)),
            level=logging.ERROR,
            noiselevel=-1,
        )
        return 1
    return os.EX_OK


def emerge_main(args: Optional[list[str]] = None):
    """
    Entry point of emerge

    Processes command line arguments (default: sys.argv[1:]) and decides
    what the current run of emerge should by creating `emerge_config`
    """
    from _emerge.actions import load_emerge_config, run_action
    from _emerge.emergelog import emergelog
    from _emerge.help import emerge_help
    from portage.output import xtermTitleReset
    from portage.util import writemsg_level

    if args is None:
        args = sys.argv[1:]

    args = portage._decode_argv(args)

    # Use system locale.
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error as e:
        writemsg_level(f"setlocale: {e}\n", level=logging.WARN)

    # Disable color until we're sure that it should be enabled (after
    # EMERGE_DEFAULT_OPTS has been parsed).
    portage.output.havecolor = 0

    if os.environ.get("PORTAGE_SHOW_HTTP_TRACE"):
        import http.client

        http.client.HTTPConnection.debuglevel = 1
        http.client.HTTPSConnection.debuglevel = 1

    # This first pass is just for options that need to be known as early as
    # possible, such as --config-root.  They will be parsed again later,
    # together with EMERGE_DEFAULT_OPTS (which may vary depending on the
    # the value of --config-root).
    myaction, myopts, myfiles = parse_opts(args, silent=True)
    if "--debug" in myopts:
        os.environ["PORTAGE_DEBUG"] = "1"
        portage.util.initialize_logger(logging.DEBUG)
    else:
        portage.util.initialize_logger()

    if "--config-root" in myopts:
        os.environ["PORTAGE_CONFIGROOT"] = myopts["--config-root"]
    if "--sysroot" in myopts:
        os.environ["SYSROOT"] = myopts["--sysroot"]
    if "--root" in myopts:
        os.environ["ROOT"] = myopts["--root"]
    if "--prefix" in myopts:
        os.environ["EPREFIX"] = myopts["--prefix"]
    if "--accept-properties" in myopts:
        os.environ["ACCEPT_PROPERTIES"] = myopts["--accept-properties"]
    if "--accept-restrict" in myopts:
        os.environ["ACCEPT_RESTRICT"] = myopts["--accept-restrict"]

    # optimize --help (no need to load config / EMERGE_DEFAULT_OPTS)
    if myaction == "help":
        emerge_help()
        return os.EX_OK
    if myaction == "moo":
        print(COWSAY_MOO.format(platform.system()))
        return os.EX_OK
    if myaction == "sync":
        # need to set this to True now in order for the repository config
        # loading to allow new repos with non-existent directories
        portage._sync_mode = True

    # Verify that /dev/null exists and is a character device as a
    # cheap early filter for obviously broken /dev/s.
    try:
        if not stat.S_ISCHR(os.stat(os.devnull).st_mode):
            writemsg_level(
                "Failed to validate a sane '/dev'.\n"
                "'/dev/null' is not a character device.\n",
                level=logging.ERROR,
                noiselevel=-1,
            )
            return 1
    except OSError:
        writemsg_level(
            "Failed to validate a sane '/dev'.\n" "'/dev/null' does not exist.\n",
            level=logging.ERROR,
            noiselevel=-1,
        )
        return 1

    # Verify that BASH process substitution works as another cheap early
    # filter. Process substitution uses '/dev/fd'.
    with open(os.devnull, "r+b") as dev_null:
        fd_pipes = {
            0: dev_null.fileno(),
            1: dev_null.fileno(),
            2: dev_null.fileno(),
        }
        exit_code = portage.process.spawn_bash(
            "[[ $(< <(echo foo) ) == foo ]]", fd_pipes=fd_pipes
        )
        if exit_code != 0:
            writemsg_level(
                "Failed to validate a sane '/dev'.\n"
                "bash process substitution doesn't work; this may be an "
                "indication of a broken '/dev/fd'.\n",
                level=logging.ERROR,
                noiselevel=-1,
            )
            return 1

    # Portage needs to ensure a sane umask for the files it creates.
    os.umask(0o22)
    emerge_config = load_emerge_config(action=myaction, args=myfiles, opts=myopts)

    # Make locale variables from configuration files (make.defaults, make.conf) affect locale of emerge process.
    for locale_var_name in (
        "LANGUAGE",
        "LC_ALL",
        "LC_ADDRESS",
        "LC_COLLATE",
        "LC_CTYPE",
        "LC_IDENTIFICATION",
        "LC_MEASUREMENT",
        "LC_MESSAGES",
        "LC_MONETARY",
        "LC_NAME",
        "LC_NUMERIC",
        "LC_PAPER",
        "LC_TELEPHONE",
        "LC_TIME",
        "LANG",
    ):
        locale_var_value = emerge_config.running_config.settings.get(locale_var_name)
        if locale_var_value is not None:
            os.environ.setdefault(locale_var_name, locale_var_value)
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error as e:
        writemsg_level(f"setlocale: {e}\n", level=logging.WARN)

    tmpcmdline = []
    if "--ignore-default-opts" not in myopts:
        tmpcmdline.extend(
            shlex.split(
                emerge_config.target_config.settings.get("EMERGE_DEFAULT_OPTS", "")
            )
        )
    tmpcmdline.extend(args)
    emerge_config.action, emerge_config.opts, emerge_config.args = parse_opts(
        tmpcmdline
    )

    try:
        return run_action(emerge_config)
    finally:
        # Call destructors for our portdbapi instances.
        for x in emerge_config.trees.values():
            if "porttree" in x.lazy_items:
                continue
            x["porttree"].dbapi.close_caches()

        # This gets out final log message in before we quit.
        xterm_titles = "notitles" not in emerge_config.target_config.settings.features
        if "--pretend" not in emerge_config.opts:
            emergelog(xterm_titles, " *** terminating.")

        if xterm_titles:
            xtermTitleReset()
