// Include Files							/*{{{*/
#include <config.h>

#include <apt-pkg/cmndline.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/debversion.h>
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/init.h>
#include <apt-pkg/pkgsystem.h>
#include <apt-pkg/strutl.h>

#include <apt-private/private-cmndline.h>
#include <apt-private/private-main.h>

#include <cassert>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <unistd.h>

#include <algorithm>
#include <iomanip>
#include <optional>
#include <utility>
#include <vector>

#include <apti18n.h>
									/*}}}*/

APT_NONNULL(1, 2)
static bool CmdMatches_fn(char const *const Cmd, char const *const Match)
{
   return strcmp(Cmd, Match) == 0;
}
template <typename... Tail>
APT_NONNULL(1, 2)
static bool CmdMatches_fn(char const *const Cmd, char const *const Match, Tail... MoreMatches)
{
   return CmdMatches_fn(Cmd, Match) || CmdMatches_fn(Cmd, MoreMatches...);
}
#define addArg(w, x, y, z) Args.emplace_back(CommandLine::MakeArgs(w, x, y, z))
#define CmdMatches(...) (Cmd != nullptr && CmdMatches_fn(Cmd, __VA_ARGS__))

struct option
{
   const char shrt;
   const char *lng;
   const char *option;
   const char *description = nullptr;
   const char flag = 0;
};
struct command
{
   const std::initializer_list<std::string_view> commands;
   const std::initializer_list<option> options;
};
struct binary
{
   APT_CMD binary;
   const std::initializer_list<command> commands;
   const std::initializer_list<option> options;
   const std::initializer_list<APT_CMD> inherits = {};
};

using commands = std::initializer_list<command>;
using options = std::initializer_list<option>;

constexpr options globalOptions{
   {'h', "help", "help", N_("Show this help message"), 0},
   {'v', "version", "version", N_("Show the version"), 0},
   {0, "color", "APT::Color", N_("Enable color output"), 0},
   {'q', "quiet", "quiet", N_("Output less information"), CommandLine::IntLevel},
   {0, "audit", "APT::Audit", N_("Audit output"), 0},
   {'q', "silent", "quiet", nullptr, CommandLine::IntLevel},
   {'c', "config-file", 0, "Read a configuration file", CommandLine::ConfigFile},
   {'o', "option", 0, "Set a configuration option", CommandLine::ArbItem},
   {0, "cli-version", "APT::Version", N_("Set CLI version"), CommandLine::HasArg},
};

constexpr std::initializer_list<binary> binaries{
   binary{
      APT_CMD::APT,
      {
	 command{
	    {"list"},
	    {
	       {'i', "installed", "APT::Cmd::Installed", N_("Print only installed packages"), 0},
	       {'u', "upgradeable", "APT::Cmd::Upgradable", N_("Print only upgradeable packages"), 0},
	       {0, "upgradable", "APT::Cmd::Upgradable", nullptr, 0},
	       {0, "manual-installed", "APT::Cmd::Manual-Installed", N_("Print only manually installed packages"), 0},
	       {'v', "verbose", "APT::Cmd::List-Include-Summary", N_("Include summary"), 0},
	       {'a', "all-versions", "APT::Cmd::All-Versions", N_("Show all versions"), 0},
	       {'t', "target-release", "APT::Default-Release", N_("Set the target release"), CommandLine::HasArg},
	       {'t', "default-release", "APT::Default-Release", nullptr, CommandLine::HasArg},
	       {'S', "snapshot", "APT::Snapshot", N_("Snapshot to use"), CommandLine::HasArg},
	    },
	 },
	 command{
	    {"show", "info"},
	    {
	       {'a', "all-versions", "APT::Cache::AllVersions", N_("Show all versions"), 0},
	       {'f', "full", "APT::Cache::ShowFull", N_("Show the full record"), 0},
	       {'S', "snapshot", "APT::Snapshot", N_("Snapshot to use"), CommandLine::HasArg},
	    },
	 },
      },
      options{
	 {0, "with-source", "APT::Sources::With::", N_("Configure an ephemeral source file"), CommandLine::HasArg},
      },
      {APT_CMD::APT_GET, APT_CMD::APT_CACHE},
   },
   binary{
      APT_CMD::APT_CACHE,
      {
	 command{
	    {"depends", "rdepends"},
	    {
	       {'i', "important", "APT::Cache::Important", N_("Print only important dependencies")},
	       {0, "installed", "APT::Cache::Installed", N_("Print only installed dependencies")},
	       {0, "pre-depends", "APT::Cache::ShowPre-Depends", N_("Print Pre-Depends")},
	       {0, "depends", "APT::Cache::ShowDepends", N_("Print Depends")},
	       {0, "recommends", "APT::Cache::ShowRecommends", N_("Print Recommends")},
	       {0, "suggests", "APT::Cache::ShowSuggests", N_("Print Suggests")},
	       {0, "replaces", "APT::Cache::ShowReplaces", N_("Print Replaces")},
	       {0, "breaks", "APT::Cache::ShowBreaks", N_("Print Breaks")},
	       {0, "conflicts", "APT::Cache::ShowConflicts", N_("Print Conflicts")},
	       {0, "enhances", "APT::Cache::ShowEnhances", N_("Print Enhances")},
	       {0, "recurse", "APT::Cache::RecurseDepends", N_("Print dependencies recursively")},
	       {0, "implicit", "APT::Cache::ShowImplicit", N_("Print implicit dependencies")},
	    },
	 },
	 command{
	    {"search"},
	    {
	       {'n', "names-only", "APT::Cache::NamesOnly", N_("Only search names")},
	       {'f', "full", "APT::Cache::ShowFull", N_("Show the full record")},
	    },
	 },
	 command{
	    {"show", "info"},
	    {
	       {'a', "all-versions", "APT::Cache::AllVersions", N_("Show all versions")},
	    },
	 },
	 command{
	    {"pkgnames"},
	    {
	       {0, "all-names", "APT::Cache::AllNames", N_("Show virtual packages and missing dependencies")},
	    },
	 },
	 command{
	    {"unmet"},
	    {
	       {'i', "important", "APT::Cache::Important", N_("Print only important dependencies")},
	    },
	 },
	 command{
	    {"showsrc"},
	    {
	       {0, "only-source", "APT::Cache::Only-Source", N_("Only query source package names")},
	    },
	 },
	 command{
	    {"gencaches", "showpkg", "stats", "dump", "dumpavail", "showauto", "policy", "madison"},
	    {},
	 },
      },
      options{
	 {'g', "generate", "APT::Cache::Generate", N_("Generate the cache")},
	 {'t', "target-release", "APT::Default-Release", N_("Set the target release"), CommandLine::HasArg},
	 {'t', "default-release", "APT::Default-Release", nullptr, CommandLine::HasArg},
	 {'S', "snapshot", "APT::Snapshot", N_("Snapshot to use"), CommandLine::HasArg},

	 {'p', "pkg-cache", "Dir::Cache::pkgcache", N_("Set the location of the pkgcache.bin"), CommandLine::HasArg},
	 {'s', "src-cache", "Dir::Cache::srcpkgcache", N_("Set the location of the srcpkgcache.bin"), CommandLine::HasArg},
	 {0, "with-source", "APT::Sources::With::", N_("Configure an ephemeral source file"), CommandLine::HasArg},
      },
   },
   binary{
      APT_CMD::APT_CDROM,
      commands{
	 {
	    {"add", "ident"},
	    {
	       {0, "auto-detect", "Acquire::cdrom::AutoDetect", N_("Auto detect the CD"), CommandLine::Boolean},
	       {'d', "cdrom", "Acquire::cdrom::mount", N_("CD mount point"), CommandLine::HasArg},
	       {'r', "rename", "APT::CDROM::Rename", N_("Rename the CD")},
	       {'m', "no-mount", "APT::CDROM::NoMount", N_("Do not mount automatically")},
	       {'f', "fast", "APT::CDROM::Fast", N_("Do a fast operation")},
	       {'n', "just-print", "APT::CDROM::NoAct", N_("Just print, do not act")},
	       {'n', "recon", "APT::CDROM::NoAct", N_("Just print, do not act")},
	       {'n', "no-act", "APT::CDROM::NoAct", N_("Just print, do not act")},
	       {'a', "thorough", "APT::CDROM::Thorough", N_("Be thorough")},
	    },
	 },
      },
      options{},
   },
   binary{
      APT_CMD::APT_DUMP_SOLVER,
      commands{},
      options{
	 {0, "user", "APT::Solver::RunAsUser", N_("Run solver as the specified user"), CommandLine::HasArg},
      },
   },
   binary{
      APT_CMD::APT_INTERNAL_PLANNER,
      commands{},
      options{},
   },
   binary{
      APT_CMD::APT_INTERNAL_SOLVER,
      commands{},
      options{},
   },
   binary{
      APT_CMD::APT_CONFIG,
      {
	 command{
	    {"dump"},
	    {
	       {0, "empty", "APT::Config::Dump::EmptyValue", N_("Include empty values in output"), CommandLine::Boolean},
	       {0, "format", "APT::Config::Dump::Format", N_("Output format (shell, ...)"), CommandLine::HasArg},
	    },
	 },
	 command{
	    {"shell"},
	    {},
	 },
      },
      options{},
   },
   binary{
      APT_CMD::APT_EXTRACTTEMPLATES,
      commands{},
      options{
	 {'t', "tempdir", "APT::ExtractTemplates::TempDir", N_("Temporary directory for extracted templates"), CommandLine::HasArg},
      },
   },
   binary{
      APT_CMD::APT_FTPARCHIVE,
      commands{},
      options{
	 {0, "md5", "APT::FTPArchive::MD5", N_("Generate MD5 checksums"), 0},
	 {0, "sha1", "APT::FTPArchive::SHA1", N_("Generate SHA1 checksums"), 0},
	 {0, "sha256", "APT::FTPArchive::SHA256", N_("Generate SHA256 checksums"), 0},
	 {0, "sha512", "APT::FTPArchive::SHA512", N_("Generate SHA512 checksums"), 0},
	 {'d', "db", "APT::FTPArchive::DB", N_("Cache database"), CommandLine::HasArg},
	 {'s', "source-override", "APT::FTPArchive::SourceOverride", N_("Source override file"), CommandLine::HasArg},
	 {0, "delink", "APT::FTPArchive::DeLinkAct", N_("Enable delinking of files"), 0},
	 {0, "readonly", "APT::FTPArchive::ReadOnlyDB", N_("Make cache read-only"), 0},
	 {0, "contents", "APT::FTPArchive::Contents", N_("Generate the contents file"), 0},
	 {'a', "arch", "APT::FTPArchive::Architecture", N_("Only accept files for the given architecture"), CommandLine::HasArg},
      },
   },
   binary{
      APT_CMD::APT_SORTPKG,
      commands{},
      options{
	 {'s', "source", "APT::SortPkgs::Source", N_("Sort source package files"), 0},
      },
   },
   binary{
      APT_CMD::RRED,
      commands{},
      options{
	 {'t', nullptr, "Rred::T", nullptr, 0},
	 {'f', nullptr, "Rred::F", nullptr, 0},
	 {'C', "compress", "Rred::Compress", N_("Set compression algorithm"), CommandLine::HasArg},
      },
   },
   binary{
      APT_CMD::APT_HELPER,
      {
	 command{
	    {"cat-file"},
	    {
	       {'C', "compress", "Apt-Helper::Cat-File::Compress", N_("Set compression algorithm"), CommandLine::HasArg},
	    },
	 },
      },
      options{},
   },
   binary{
      APT_CMD::APT_MARK,
      {
	 command{
	    {"auto", "manual", "hold", "unhold", "markauto", "unmarkauto", "minimize-manual",
	     "showauto", "showmanual", "showhold", "showholds", "showheld"},
	    {
	       {'f', "file", "Dir::State::extended_states", N_("Read/write states from the given file"), CommandLine::HasArg},
	    },
	 },
	 command{
	    {"showinstall", "showinstalls", "showremove", "showremoves",
	     "showdeinstall", "showdeinstalls", "showpurge", "showpurges"},
	    {},
	 },
	 command{
	    {"markauto", "unmarkauto"},
	    {
	       {'v', "verbose", "APT::MarkAuto::Verbose", N_("Verbose output"), 0},
	    },
	 },
	 command{
	    {"minimize-manual"},
	    {
	       {'y', "yes", "APT::Get::Assume-Yes", N_("Automatic yes to prompts"), 0},
	       {'y', "assume-yes", "APT::Get::Assume-Yes", nullptr, 0},
	       {0, "assume-no", "APT::Get::Assume-No", N_("Automatic no to prompts"), 0},
	    },
	 },
	 command{
	    {"auto", "manual", "hold", "unhold", "markauto", "unmarkauto", "minimize-manual",
	     "install", "reinstall", "remove", "deinstall", "purge"},
	    {
	       {'s', "simulate", "APT::Mark::Simulate", N_("No action; perform a simulation"), 0},
	       {'s', "just-print", "APT::Mark::Simulate", nullptr, 0},
	       {'s', "recon", "APT::Mark::Simulate", nullptr, 0},
	       {'s', "dry-run", "APT::Mark::Simulate", nullptr, 0},
	       {'s', "no-act", "APT::Mark::Simulate", nullptr, 0},
	    },
	 },
      },
      options{
	 {0, "with-source", "APT::Sources::With::", N_("Configure an ephemeral source file"), CommandLine::HasArg},
      },
   },
   binary{
      APT_CMD::APT_GET,
      {
	 command{
	    {"install", "reinstall", "remove", "purge", "upgrade", "dist-upgrade",
	     "dselect-upgrade", "autoremove", "auto-remove", "autopurge", "full-upgrade"},
	    {
	       {0, "show-progress", "DpkgPM::Progress", N_("Show user-friendly progress information"), 0},
	       {'f', "fix-broken", "APT::Get::Fix-Broken", N_("Fix broken dependencies"), 0},
	       {0, "purge", "APT::Get::Purge", N_("Use purge instead of remove"), 0},
	       {'V', "verbose-versions", "APT::Get::Show-Versions", N_("Show verbose version information"), 0},
	       {0, "list-columns", "APT::Get::List-Columns", N_("Show list columns"), 0},
	       {0, "autoremove", "APT::Get::AutomaticRemove", N_("Remove automatically installed packages no longer needed"), 0},
	       {0, "auto-remove", "APT::Get::AutomaticRemove", nullptr, 0},
	       {0, "reinstall", "APT::Get::ReInstall", N_("Reinstall packages"), 0},
	       {0, "solver", "APT::Solver", N_("Set the dependency solver"), CommandLine::HasArg},
	       {0, "strict-pinning", "APT::Solver::Strict-Pinning", N_("Apply strict pinning"), 0},
	       {0, "planner", "APT::Planner", N_("Set the dependency planner"), CommandLine::HasArg},
	       {0, "comment", "APT::History::Comment", N_("Add a comment to history"), CommandLine::HasArg},
	       {'U', "update", "APT::Update", N_("Update package lists"), 0},
	    },
	 },
	 command{
	    {"upgrade"},
	    {
	       {0, "new-pkgs", "APT::Get::Upgrade-Allow-New", N_("Allow installing new packages for upgraded dependencies"), CommandLine::Boolean},
	    },
	 },
	 command{
	    {"update"},
	    {
	       {0, "list-cleanup", "APT::Get::List-Cleanup", N_("Clean up old list files"), 0},
	       {0, "allow-insecure-repositories", "Acquire::AllowInsecureRepositories", N_("Allow insecure repositories"), 0},
	       {0, "allow-weak-repositories", "Acquire::AllowWeakRepositories", N_("Allow weak repositories"), 0},
	       {0, "allow-releaseinfo-change", "Acquire::AllowReleaseInfoChange", N_("Allow Release info changes"), 0},
	       {0, "allow-releaseinfo-change-origin", "Acquire::AllowReleaseInfoChange::Origin", N_("Allow changes to Origin field"), 0},
	       {0, "allow-releaseinfo-change-label", "Acquire::AllowReleaseInfoChange::Label", N_("Allow changes to Label field"), 0},
	       {0, "allow-releaseinfo-change-version", "Acquire::AllowReleaseInfoChange::Version", N_("Allow changes to Version field"), 0},
	       {0, "allow-releaseinfo-change-codename", "Acquire::AllowReleaseInfoChange::Codename", N_("Allow changes to Codename field"), 0},
	       {0, "allow-releaseinfo-change-suite", "Acquire::AllowReleaseInfoChange::Suite", N_("Allow changes to Suite field"), 0},
	       {0, "allow-releaseinfo-change-defaultpin", "Acquire::AllowReleaseInfoChange::DefaultPin", N_("Allow changes to DefaultPin field"), 0},
	       {'e', "error-on", "APT::Update::Error-Mode", N_("Fail on the specified error"), CommandLine::HasArg},
	    },
	 },
	 command{
	    {"source"},
	    {
	       {'a', "host-architecture", "APT::Get::Host-Architecture", N_("Set host architecture"), CommandLine::HasArg},
	       {'b', "compile", "APT::Get::Compile", N_("Compile source packages"), 0},
	       {'b', "build", "APT::Get::Compile", nullptr, 0},
	       {'P', "build-profiles", "APT::Build-Profiles", N_("Set build profiles"), CommandLine::HasArg},
	       {0, "diff-only", "APT::Get::Diff-Only", N_("Download only the diff"), 0},
	       {0, "debian-only", "APT::Get::Diff-Only", nullptr, 0},
	       {0, "tar-only", "APT::Get::Tar-Only", N_("Download only the tar"), 0},
	       {0, "dsc-only", "APT::Get::Dsc-Only", N_("Download only the dsc"), 0},
	    },
	 },
	 command{
	    {"build-dep", "satisfy"},
	    {
	       {'a', "host-architecture", "APT::Get::Host-Architecture", N_("Set host architecture"), CommandLine::HasArg},
	       {'P', "build-profiles", "APT::Build-Profiles", N_("Set build profiles"), CommandLine::HasArg},
	       {0, "purge", "APT::Get::Purge", N_("Use purge instead of remove"), 0},
	       {0, "solver", "APT::Solver", N_("Set the dependency solver"), CommandLine::HasArg},
	       {0, "strict-pinning", "APT::Solver::Strict-Pinning", N_("Apply strict pinning"), 0},
	       {'f', "fix-broken", "APT::Get::Fix-Broken", N_("Fix broken dependencies"), 0},
	    },
	 },
	 command{
	    {"build-dep"},
	    {
	       {0, "arch-only", "APT::Get::Arch-Only", N_("Only process architecture-dependent build dependencies"), 0},
	       {0, "indep-only", "APT::Get::Indep-Only", N_("Only process architecture-independent build dependencies"), 0},
	    },
	 },
	 command{
	    {"indextargets"},
	    {
	       {0, "format", "APT::Get::IndexTargets::Format", N_("Output format"), CommandLine::HasArg},
	       {0, "release-info", "APT::Get::IndexTargets::ReleaseInfo", N_("Show release info"), 0},
	    },
	 },
	 command{
	    {"download", "changelog", "markauto", "unmarkauto"},
	    {},
	 },
	 command{
	    {"install", "reinstall", "remove", "purge", "upgrade", "dist-upgrade",
	     "dselect-upgrade", "autoremove", "auto-remove", "autopurge", "full-upgrade",
	     "source", "build-dep", "satisfy",
	     "clean", "autoclean", "auto-clean", "distclean", "dist-clean", "check"},
	    {
	       {'s', "simulate", "APT::Get::Simulate", N_("No action; perform a simulation"), 0},
	       {'s', "just-print", "APT::Get::Simulate", nullptr, 0},
	       {'s', "recon", "APT::Get::Simulate", nullptr, 0},
	       {'s', "dry-run", "APT::Get::Simulate", nullptr, 0},
	       {'s', "no-act", "APT::Get::Simulate", nullptr, 0},
	    },
	 },
      },
      options{
	 {'d', "download-only", "APT::Get::Download-Only", N_("Download only; do not install or unpack"), 0},
	 {'y', "yes", "APT::Get::Assume-Yes", N_("Automatic yes to prompts"), 0},
	 {'y', "assume-yes", "APT::Get::Assume-Yes", nullptr, 0},
	 {0, "assume-no", "APT::Get::Assume-No", N_("Automatic no to prompts"), 0},
	 {'u', "show-upgraded", "APT::Get::Show-Upgraded", N_("Show upgraded packages"), 0},
	 {'t', "target-release", "APT::Default-Release", N_("Set the target release"), CommandLine::HasArg},
	 {'t', "default-release", "APT::Default-Release", nullptr, CommandLine::HasArg},
	 {'S', "snapshot", "APT::Snapshot", N_("Snapshot to use"), CommandLine::HasArg},
	 {0, "download", "APT::Get::Download", N_("Download packages"), 0},
	 {0, "ignore-missing", "APT::Get::Fix-Missing", nullptr, 0},
	 {'m', "fix-missing", "APT::Get::Fix-Missing", N_("Ignore packages that cannot be retrieved"), 0},
	 {0, "ignore-hold", "APT::Ignore-Hold", N_("Ignore held packages"), 0},
	 {0, "upgrade", "APT::Get::upgrade", N_("Upgrade packages"), 0},
	 {0, "only-upgrade", "APT::Get::Only-Upgrade", N_("Only upgrade packages"), 0},
	 {0, "allow-change-held-packages", "APT::Get::allow-change-held-packages", N_("Allow changing held packages"), CommandLine::Boolean},
	 {0, "allow-remove-essential", "APT::Get::allow-remove-essential", N_("Allow removing essential packages"), CommandLine::Boolean},
	 {0, "allow-downgrades", "APT::Get::allow-downgrades", N_("Allow downgrading packages"), CommandLine::Boolean},
	 {0, "force-yes", "APT::Get::force-yes", N_("Force yes (deprecated)"), 0},
	 {0, "print-uris", "APT::Get::Print-URIs", N_("Print URIs"), 0},
	 {0, "trivial-only", "APT::Get::Trivial-Only", N_("Only perform trivial operations"), 0},
	 {0, "mark-auto", "APT::Get::Mark-Auto", N_("Mark packages as automatically installed"), 0},
	 {0, "remove", "APT::Get::Remove", N_("Remove packages"), 0},
	 {0, "only-source", "APT::Get::Only-Source", N_("Only query source package names"), 0},
	 {0, "allow-unauthenticated", "APT::Get::AllowUnauthenticated", N_("Allow unauthenticated packages"), 0},
	 {0, "install-recommends", "APT::Install-Recommends", N_("Install recommended packages"), CommandLine::Boolean},
	 {0, "install-suggests", "APT::Install-Suggests", N_("Install suggested packages"), CommandLine::Boolean},
	 {0, "fix-policy", "APT::Get::Fix-Policy-Broken", N_("Fix broken policy dependencies"), 0},
	 {0, "with-source", "APT::Sources::With::", N_("Configure an ephemeral source file"), CommandLine::HasArg},
      },
   },
};

static bool addArguments(APT_CMD Binary, std::vector<CommandLine::Args> &Args, char const *const Cmd) /*{{{*/
{
   auto binary = std::ranges::find_if(binaries, [Binary](auto b) noexcept
				      { return b.binary == Binary; });
   if (binary == binaries.end())
      return false;

   if (Cmd)
   {
      bool matched = false;
      for (auto &command : binary->commands)
	 if (std::ranges::contains(command.commands, std::string_view(Cmd)))
	 {
	    for (auto &option : command.options)
	       addArg(option.shrt, option.lng, option.option, option.flag);
	    matched = true;
	 }

      if (!matched)
	 for (auto inherit : binary->inherits)
	    if (addArguments(inherit, Args, Cmd))
	       break;
   }

   bool addedArgs = not Args.empty();
   for (auto &option : binary->options)
      addArg(option.shrt, option.lng, option.option, option.flag);

   return addedArgs;
}
									/*}}}*/
std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const * const Cmd)/*{{{*/
{
   std::vector<CommandLine::Args> Args;
   Args.reserve(50);
   if (Cmd == nullptr || strcmp(Cmd, "help") != 0)
      addArguments(Program, Args, Cmd);

   for (auto &option : globalOptions)
      addArg(option.shrt, option.lng, option.option, option.flag);
   addArg(0, nullptr, nullptr, 0);

   return Args;
}
									/*}}}*/
#undef addArg

struct OptionPrinter
{
   size_t const valueLength = strlen(_("<value>"));
   size_t width = 0;
   const binary *bin;
   std::vector<option const *> commandOptions;

   OptionPrinter(const binary &b) : bin(&b)
   {
      calculate_width();
   }

   OptionPrinter(const binary &b, char const *const cmd) : bin(&b)
   {
      if (auto const *commandBinary =
	     collect_command_options(b, cmd, commandOptions);
	  commandBinary != nullptr)
	 bin = commandBinary;

      calculate_width();
   }

   static bool has_argument(option const &o)
   {
      return (o.flag & CommandLine::HasArg) != 0;
   }

   size_t option_width(option const &o) const
   {
      if (o.lng == nullptr)
	 return 0;

      size_t result = strlen(o.lng);
      if (has_argument(o))
	 result += 1 + valueLength;
      return result;
   }

   static std::string long_option(option const &o)
   {
      if (o.lng == nullptr)
	 return {};

      std::string result{o.lng};
      if (has_argument(o))
      {
	 result += " ";
	 result += _("<value>");
      }
      return result;
   }

   static binary const *collect_command_options(
      binary const &candidate,
      char const *const cmd,
      std::vector<option const *> &options)
   {
      bool matched = false;

      for (auto const &command : candidate.commands)
      {
	 if (not std::ranges::contains(
		command.commands, std::string_view(cmd)))
	    continue;

	 matched = true;
	 for (auto const &option : command.options)
	    options.push_back(&option);
      }

      if (matched)
	 return &candidate;

      for (auto const inherit : candidate.inherits)
      {
	 auto const inherited = std::ranges::find_if(
	    binaries, [inherit](auto const &b) noexcept
	    { return b.binary == inherit; });

	 if (inherited == binaries.end())
	    continue;

	 auto const *commandBinary =
	    collect_command_options(*inherited, cmd, options);
	 if (commandBinary != nullptr)
	    return commandBinary;
      }

      return nullptr;
   }

   void calculate_width()
   {
      for (auto const *option : commandOptions)
      {
	 if (option->description != nullptr)
	    width = std::max(option_width(*option), width);
      }

      for (auto const &option : bin->options)
      {
	 if (option.description != nullptr)
	    width = std::max(option_width(option), width);
      }

      for (auto const &option : globalOptions)
      {
	 if (option.description == nullptr ||
	     option.flag == CommandLine::ArbItem ||
	     option.flag == CommandLine::ConfigFile)
	    continue;

	 width = std::max(option_width(option), width);
      }
   }

   void print_command() const
   {
      bool hasOptions = false;
      for (auto const *option : commandOptions)
      {
	 if (option->description != nullptr)
	 {
	    hasOptions = true;
	    break;
	 }
      }

      if (hasOptions)
      {
	 std::cout << std::endl
		   << _("Options:") << std::endl;
	 for (auto const *option : commandOptions)
	 {
	    if (option->description == nullptr)
	       continue;

	    print_option(*option);
	 }
      }

      print_common();
   }

   void print_common() const
   {
      std::cout << std::endl
		<< _("Common options:") << std::endl;

      for (auto const &option : bin->options)
      {
	 if (option.description == nullptr)
	    continue;
	 print_option(option);
      }

      for (auto const &option : globalOptions)
      {
	 if (option.description == nullptr ||
	     option.flag == CommandLine::ArbItem ||
	     option.flag == CommandLine::ConfigFile)
	    continue;
	 print_option(option);
      }
   }

   void print_option(option const &o) const
   {
      auto const longOption = long_option(o);

      if (o.shrt != 0 && o.lng != nullptr)
	 std::cout << "    -" << o.shrt << ", --"
		   << std::left << std::setw(width) << longOption
		   << "  " << _(o.description) << std::endl;
      else if (o.lng != nullptr)
	 std::cout << "        --"
		   << std::left << std::setw(width) << longOption
		   << "  " << _(o.description) << std::endl;
      else if (o.shrt != 0)
      {
	 std::cout << "    -" << o.shrt;
	 if (has_argument(o))
	    std::cout << " " << _("<value>");
	 std::cout << "  " << _(o.description) << std::endl;
      }
   }
};

static void ShowHelpListCommands(std::vector<aptDispatchWithHelp> const &Cmds)/*{{{*/
{
   if (Cmds.empty() || Cmds[0].Match == nullptr)
      return;
   std::cout << std::endl << _("Most used commands:") << std::endl;
   for (auto const &c: Cmds)
   {
      if (c.Help == nullptr)
	 continue;
      std::cout << "  " << c.Match << " - " << c.Help << std::endl;
   }
}
									/*}}}*/
static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector<aptDispatchWithHelp> const &Cmds, /*{{{*/
			   bool (*ShowHelp)(CommandLine &), char const *const CmdCalled)
{
   std::cout << PACKAGE << " " << PACKAGE_VERSION << " (" << COMMON_ARCH << ")" << std::endl;
   if (_config->FindB("version") == true && Binary != APT_CMD::APT_GET)
      return true;

   // Normalize `apt help <command>` to the same command name passed by
   // `apt <command> --help`, so both forms use identical help rendering.
   char const *HelpCommand = CmdCalled;
   if (HelpCommand != nullptr &&
       strcmp(HelpCommand, "help") == 0 &&
       CmdL.FileSize() > 1)
      HelpCommand = CmdL.FileList[1];

   // A recognized command remains command-specific even when it has no
   // additional prose; its declarative options can still be rendered.
   bool commandMatched = false;
   if (HelpCommand != nullptr)
   {
      for (auto const &c : Cmds)
      {
	 if (c.Match == nullptr ||
	     strcmp(c.Match, HelpCommand) != 0)
	    continue;

	 commandMatched = true;
	 if (c.ShowHelp != nullptr &&
	     c.ShowHelp(CmdL) == false)
	    return false;
	 break;
      }
   }

   if (not commandMatched)
   {
      if (ShowHelp(CmdL) == false)
	 return false;
   }

   if (_config->FindB("version") == true ||
       Binary == APT_CMD::APT_FTPARCHIVE)
      return true;

   auto bin = std::ranges::find_if(
      binaries, [Binary](auto const &b) noexcept
      { return b.binary == Binary; });

   if (commandMatched)
   {
      if (bin != binaries.end())
	 OptionPrinter(*bin, HelpCommand).print_command();
   }
   else
   {
      ShowHelpListCommands(Cmds);
      if (bin != binaries.end())
	 OptionPrinter(*bin).print_common();
   }
   std::cout << std::endl;
   char const * cmd = nullptr;
   switch (Binary)
   {
      case APT_CMD::APT: cmd = "apt(8)"; break;
      case APT_CMD::APT_CACHE: cmd = "apt-cache(8)"; break;
      case APT_CMD::APT_CDROM: cmd = "apt-cdrom(8)"; break;
      case APT_CMD::APT_CONFIG: cmd = "apt-config(8)"; break;
      case APT_CMD::APT_DUMP_SOLVER: cmd = nullptr; break;
      case APT_CMD::APT_EXTRACTTEMPLATES: cmd = "apt-extracttemplates(1)"; break;
      case APT_CMD::APT_FTPARCHIVE: cmd = "apt-ftparchive(1)"; break;
      case APT_CMD::APT_GET: cmd = "apt-get(8)"; break;
      case APT_CMD::APT_HELPER: cmd = nullptr; break;
      case APT_CMD::APT_INTERNAL_PLANNER: cmd = nullptr; break;
      case APT_CMD::APT_INTERNAL_SOLVER: cmd = nullptr; break;
      case APT_CMD::APT_MARK: cmd = "apt-mark(8)"; break;
      case APT_CMD::APT_SORTPKG: cmd = "apt-sortpkgs(1)"; break;
      case APT_CMD::RRED: cmd = nullptr; break;
   }
   if (commandMatched)
   {
      if (cmd != nullptr)
	 ioprintf(std::cout,
		  _("See %s, apt.conf(5), sources.list(5), apt_preferences(5),\n"
		    "and apt-secure(8) for more information.\n"),
		  cmd);
   }
   else
   {
      if (cmd != nullptr)
	 ioprintf(std::cout, _("See %s for more information about the available commands."), cmd);
      if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER &&
	  Binary != APT_CMD::APT_INTERNAL_PLANNER && Binary != APT_CMD::RRED)
	 std::cout << std::endl
		   << _("Configuration options and syntax is detailed in apt.conf(5).\n"
			"Information about how to configure sources can be found in sources.list(5).\n"
			"Package and version choices can be expressed via apt_preferences(5).\n"
			"Security details are available in apt-secure(8).\n");
      if (Binary == APT_CMD::APT_GET || Binary == APT_CMD::APT)
	 std::cout << std::right << std::setw(70) << _("This APT has Super Cow Powers.") << std::endl;
      else if (Binary == APT_CMD::APT_HELPER || Binary == APT_CMD::APT_DUMP_SOLVER)
	 std::cout << std::right << std::setw(70) << _("This APT helper has Super Meep Powers.") << std::endl;
   }
   return true;
}
									/*}}}*/
static void BinarySpecificConfiguration(char const * const Binary)	/*{{{*/
{
   auto const binary = flNotDir(Binary);
   if (binary == "apt-cdrom" || binary == "apt-config")
   {
      _config->CndSet("Binary::apt-cdrom::APT::Internal::OpProgress::EraseLines", false);
   }
   _config->Set("Binary", binary);

   // Version specific configuration
   _config->CndSet("Version::1.2::APT::Color",
		   getenv("NO_COLOR") == nullptr && getenv("APT_NO_COLOR") == nullptr);
   _config->CndSet("Version::3.0::APT::Output-Version", 30);
   _config->CndSet("Version::1.1::APT::Cache::Show::Version", 2);
   _config->CndSet("Version::1.1::APT::Cache::AllVersions", false);
   _config->CndSet("Version::1.1::APT::Cache::ShowVirtuals", true);
   _config->CndSet("Version::1.1::APT::Cache::Search::Version", 2);
   _config->CndSet("Version::1.1::APT::Cache::ShowDependencyType", true);
   _config->CndSet("Version::1.1::APT::Cache::ShowVersion", true);
   _config->CndSet("Version::1.1::APT::Get::Upgrade-Allow-New", true);
   _config->CndSet("Version::1.1::APT::Cmd::Show-Update-Stats", true);
   _config->CndSet("Version::1.2::DPkg::Progress-Fancy", true);
   _config->CndSet("Version::1.2::APT::Keep-Downloaded-Packages", false);
   _config->CndSet("Version::1.5::APT::Get::Update::InteractiveReleaseInfoChanges", true);
   _config->CndSet("Version::2.0::APT::Cmd::Pattern-Only", true);
   _config->CndSet("Version::3.0::Pager", true);
   _config->CndSet("Version::0.31::APT::Solver", "3.0");
   _config->CndSet("Version::1.21::APT::Solver", "3.0");
   _config->CndSet("Version::2.11::APT::Solver", "3.0");
   _config->CndSet("Version::3.1::APT::Solver", "3.0");

   if (isatty(STDIN_FILENO))
      _config->CndSet("Version::2.0::Dpkg::Lock::Timeout", -1);
   else
      _config->CndSet("Version::2.0::Dpkg::Lock::Timeout", 120);
}
									/*}}}*/
static void BinaryCommandSpecificConfiguration(char const * const Binary, char const * const Cmd)/*{{{*/
{
   auto const binary = flNotDir(Binary);
   if ((binary == "apt" || binary == "apt-get") && CmdMatches("upgrade", "dist-upgrade", "full-upgrade"))
   {
      //FIXME: the option is documented to apply only for install/remove, so
      // we force it false for configuration files where users can be confused if
      // we support it anyhow, but allow it on the commandline to take effect
      // even through it isn't documented as a user who doesn't want it wouldn't
      // ask for it
      _config->Set("APT::Get::AutomaticRemove", "");
   }
}
#undef CmdMatches
									/*}}}*/

static std::pair<std::optional<long unsigned int>, std::optional<long unsigned int>> parseCliVersion(std::string_view version)
{
   auto level = VectorizeString(version, '.');
   long unsigned int first = 0, second = 0;
   if (not StrToNum(level[0].data(), first, level[0].size(), 10))
      return {};
   if (level.size() <= 1)
      return {first, {}};
   if (not StrToNum(level[1].data(), second, level[1].size(), 10))
      return {};

   return {first, second};
}

bool cliVersionIsCompatible(std::string_view rawLevel, std::string_view rawPattern)
{
   auto level = parseCliVersion(rawLevel);
   auto pattern = parseCliVersion(rawPattern);

   assert(level.first);
   assert(level.second);

   // Requested an earlier major version
   if (pattern.first && *level.first > *pattern.first)
      return false;
   // Requested a latter major version
   if (pattern.first && *level.first < *pattern.first)
      return (*level.second < 10); // Keep in mind 2.10 follows 2.8, not 2.9, but 3.0 follows 2.9
   // Same major version, requested a higher pattern version (compatible usually)
   if (pattern.second && *level.second < *pattern.second)
      return (*level.second != 9); // X.9 is not compatible with X.10 onward
   // Special case for short pattern, X.9 does not migrate to X+1
   if (not pattern.second && level.second == 9)
      return false;
   // Same major version, requested a lower major version.
   if (pattern.second && *level.second > *pattern.second)
      return false;

   return true;
}

std::vector<CommandLine::Dispatch> ParseCommandLine(CommandLine &CmdL, APT_CMD const Binary,/*{{{*/
      Configuration * const * const Cnf, pkgSystem ** const Sys, int const argc, const char *argv[],
      bool (*ShowHelp)(CommandLine &), std::vector<aptDispatchWithHelp> (*GetCommands)(void))
{
   InitLocale(Binary);
   if (Cnf != NULL && pkgInitConfig(**Cnf) == false)
   {
      _error->DumpErrors();
      exit(100);
   }

   if (likely(argc != 0 && argv[0] != NULL))
      BinarySpecificConfiguration(argv[0]);

   std::vector<CommandLine::Dispatch> Cmds;
   std::vector<aptDispatchWithHelp> const CmdsWithHelp = GetCommands();
   if (CmdsWithHelp.empty() == false)
   {
      CommandLine::Dispatch const help = { "help", [](CommandLine &){return false;} };
      Cmds.push_back(std::move(help));
   }
   std::transform(CmdsWithHelp.begin(), CmdsWithHelp.end(), std::back_inserter(Cmds),
		  [](auto &&cmd) { return CommandLine::Dispatch{cmd.Match, cmd.Handler}; });

   char const * CmdCalled = nullptr;
   if (Cmds.empty() == false && Cmds[0].Handler != nullptr)
      CmdCalled = CommandLine::GetCommand(Cmds.data(), argc, argv);
   if (CmdCalled != nullptr)
      BinaryCommandSpecificConfiguration(argv[0], CmdCalled);

   std::string const conf = "Binary::" + _config->Find("Binary");
   _config->MoveSubTree(conf.c_str(), nullptr);

   // Args running out of scope invalidates the pointer stored in CmdL,
   // but we don't use the pointer after this function, so we ignore
   // this problem for now and figure something out if we have to.
   auto Args = getCommandArgs(Binary, CmdCalled);
   CmdL = CommandLine(Args.data(), _config);

   auto initVersion = []() -> bool
   {
      auto defaultVersion = parseCliVersion(PACKAGE_VERSION);
      std::string requestedVersion = _config->Find("APT::Version");
      if (requestedVersion.empty())
      {
	 if (_config->Find("Binary", "") == "apt")
	    requestedVersion = PACKAGE_VERSION;
	 else
	    strprintf(requestedVersion, "0.%lu", *defaultVersion.first * 10 + *defaultVersion.second);
      }
      else
      {
	 // Check that the requested version is compatible with our version
	 auto parsedRequestedVersion = parseCliVersion(requestedVersion);
	 // This appends a 0 to the requested version if not specified.
	 std::string cleanRequestedVersion;
	 strprintf(cleanRequestedVersion, "%lu.%lu", *parsedRequestedVersion.first, parsedRequestedVersion.second ? *parsedRequestedVersion.second : 0);
	 // Supported version "lowers" say 3.1 to 0.31, 1.21, 2.11 depending on requested major
	 std::string supportedVersion;
	 strprintf(supportedVersion, "%lu.%lu", *parsedRequestedVersion.first, (*defaultVersion.first - *parsedRequestedVersion.first) * 10 + *defaultVersion.second);
	 if (not cliVersionIsCompatible(cleanRequestedVersion, supportedVersion))
	    return _error->Error("Requested version %s is not supported in version %s (%s is)", requestedVersion.c_str(), PACKAGE_VERSION, supportedVersion.c_str());
      }
      if (auto Versions = _config->Tree("Version"))
      {
	 for (auto I = Versions->Child; I != NULL; I = I->Next)
	 {
	    auto sub = "Version::" + I->Tag;
	    if (not cliVersionIsCompatible(I->Tag, requestedVersion))
	       continue;
	    // FIXME: 2.10 should not flow into 3.0
	    _config->MoveSubTree(sub.c_str(), nullptr, false);
	 }
      }
      return true;
   };

   if (CmdL.Parse(argc,argv) == false ||
       initVersion() == false ||
       (Sys != NULL && pkgInitSystem(*_config, *Sys) == false))
   {
      if (_config->FindB("version") == true)
	 ShowCommonHelp(Binary, CmdL, CmdsWithHelp, ShowHelp, CmdCalled);

      _error->DumpErrors();
      exit(100);
   }

   if (_config->FindB("APT::Get::Force-Yes", false) == true)
   {
      _error->Warning(_("--force-yes is deprecated, use one of the options starting with --allow instead."));
   }

   if (not _config->FindB("APT::Solver::Strict-Pinning", true) && _config->Find("APT::Solver", "internal") == "internal")
   {
      _config->Set("APT::Solver", "3.0");
      _error->Notice("Automatically enabled --solver 3.0 for --no-strict-pinning");
   }

   // See if the help should be shown
   if (_config->FindB("help") == true || _config->FindB("version") == true ||
	 (CmdL.FileSize() > 0 && strcmp(CmdL.FileList[0], "help") == 0))
   {
      ShowCommonHelp(Binary, CmdL, CmdsWithHelp, ShowHelp, CmdCalled);
      exit(0);
   }
   if (Cmds.empty() == false && CmdL.FileSize() == 0)
   {
      ShowCommonHelp(Binary, CmdL, CmdsWithHelp, ShowHelp, CmdCalled);
      exit(1);
   }
   return Cmds;
}
									/*}}}*/
unsigned short DispatchCommandLine(CommandLine &CmdL, std::vector<CommandLine::Dispatch> const &Cmds)	/*{{{*/
{
   // Match the operation
   bool const returned = Cmds.empty() ? true : CmdL.DispatchArg(Cmds.data());

   // Print any errors or warnings found during parsing
   bool const Errors = _error->PendingError();
   if (_config->FindB("APT::Audit"))
      _error->DumpErrors(GlobalError::AUDIT);
   else if (_config->FindI("quiet",0) > 0)
      _error->DumpErrors();
   else
      _error->DumpErrors(GlobalError::NOTICE);
   if (returned == false || std::cout.bad() || std::cerr.bad() || std::clog.bad())
      return 100;
   return Errors == true ? 100 : 0;
}
									/*}}}*/
