diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 74d760d37b..86723b5fd3 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -13,106 +13,302 @@ namespace AppInstaller::CLI using namespace Settings; using namespace AppInstaller::Utility::literals; + namespace + { + bool ContainsArgumentFromList(const Execution::Args& args, const std::vector& argTypes) + { + return std::any_of(argTypes.begin(), argTypes.end(), [&](Execution::Args::Type arg) { return args.Contains(arg); }); + } + } + + ArgumentCommon ArgumentCommon::ForType(Execution::Args::Type type) + { + // A test ensures that all types are listed here + switch (type) + { + // Args to specify where to get app + case Execution::Args::Type::Query: + return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Manifest: + return { type, "manifest"_liv, 'm', ArgTypeCategory::Manifest }; + + // Query filtering criteria and query behavior + case Execution::Args::Type::Id: + return { type, "id"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Name: + return { type, "name"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Moniker: + return { type, "moniker"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Tag: + return { type, "tag"_liv, ArgTypeCategory::PackageQuery }; + case Execution::Args::Type::Command: + return { type, "command"_liv, "cmd"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Source: + return { type, "source"_liv, 's', ArgTypeCategory::Source }; + case Execution::Args::Type::Count: + return { type, "count"_liv, 'n', ArgTypeCategory::MultiplePackages }; + case Execution::Args::Type::Exact: + return { type, "exact"_liv, 'e', ArgTypeCategory::PackageQuery }; + + // Manifest selection behavior after an app is found + case Execution::Args::Type::Version: + return { type, "version"_liv, 'v', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Channel: + return { type, "channel"_liv, 'c', ArgTypeCategory::PackageQuery }; + + // Install behavior + case Execution::Args::Type::Interactive: + return { type, "interactive"_liv, 'i', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::Silent: + return { type, "silent"_liv, 'h', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::Locale: + return { type, "locale"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::Log: + return { type, "log"_liv, 'o', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::CustomSwitches: + return { type, "custom"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::Override: + return { type, "override"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::InstallLocation: + return { type, "location"_liv, 'l', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::InstallScope: + return { type, "scope"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallArchitecture: + return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::HashOverride: + return { type, "ignore-security-hash"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::IgnoreLocalArchiveMalwareScan: + return { type, "ignore-local-archive-malware-scan"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::AcceptPackageAgreements: + return { type, "accept-package-agreements"_liv, ArgTypeCategory::InstallerBehavior }; + case Execution::Args::Type::Rename: + return { type, "rename"_liv, 'r' }; + case Execution::Args::Type::NoUpgrade: + return { type, "no-upgrade"_liv, ArgTypeCategory::CopyFlagToSubContext }; + + // Uninstall behavior + case Execution::Args::Type::Purge: + return { type, "purge"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; + case Execution::Args::Type::Preserve: + return { type, "preserve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; + case Execution::Args::Type::ProductCode: + return { type, "product-code"_liv, ArgTypeCategory::SinglePackageQuery }; + + //Source Command + case Execution::Args::Type::SourceName: + return { type, "name"_liv, 'n' }; + case Execution::Args::Type::SourceType: + return { type, "type"_liv, 't' }; + case Execution::Args::Type::SourceArg: + return { type, "arg"_liv, 'a' }; + case Execution::Args::Type::ForceSourceReset: + return { type, "force"_liv }; + + //Hash Command + case Execution::Args::Type::HashFile: + return { type, "file"_liv, 'f' }; + case Execution::Args::Type::Msix: + return { type, "msix"_liv, 'm' }; + + //Validate Command + case Execution::Args::Type::ValidateManifest: + return { type, "manifest"_liv }; + + // Complete Command + case Execution::Args::Type::Word: + return { type, "word"_liv }; + case Execution::Args::Type::CommandLine: + return { type, "commandline"_liv }; + case Execution::Args::Type::Position: + return { type, "position"_liv }; + + // Export Command + case Execution::Args::Type::OutputFile: + return { type, "output"_liv, 'o' }; + case Execution::Args::Type::IncludeVersions: + return { type, "include-versions"_liv }; + + // Import Command + case Execution::Args::Type::ImportFile: + return { type, "import-file"_liv, 'i' }; + case Execution::Args::Type::IgnoreUnavailable: + return { type, "ignore-unavailable"_liv }; + case Execution::Args::Type::IgnoreVersions: + return { type, "ignore-versions"_liv }; + + // Setting Command + case Execution::Args::Type::AdminSettingEnable: + return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; + case Execution::Args::Type::AdminSettingDisable: + return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; + + // Upgrade command + case Execution::Args::Type::All: + return { type, "all"_liv, 'r', "recurse"_liv, ArgTypeCategory::MultiplePackages }; + case Execution::Args::Type::IncludeUnknown: + return { type, "include-unknown"_liv, 'u', "unknown"_liv }; + case Execution::Args::Type::UninstallPrevious: + return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior }; + + // Show command + case Execution::Args::Type::ListVersions: + return { type, "versions"_liv }; + + // Pin command + case Execution::Args::Type::GatedVersion: + return { type, "version"_liv, 'v', ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + case Execution::Args::Type::BlockingPin: + return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + + // Common arguments + case Execution::Args::Type::NoVT: + return { type, "no-vt"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::RetroStyle: + return { type, "retro"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::RainbowStyle: + return { type, "rainbow"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::Help: + return { type, "help"_liv, APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR }; + case Execution::Args::Type::Info: + return { type, "info"_liv }; + case Execution::Args::Type::VerboseLogs: + return { type, "verbose-logs"_liv, "verbose"_liv }; + case Execution::Args::Type::DisableInteractivity: + return { type, "disable-interactivity"_liv }; + case Execution::Args::Type::Wait: + return { type, "wait"_liv }; + case Execution::Args::Type::OpenLogs: + return { type, "open-logs"_liv, "logs"_liv }; + case Execution::Args::Type::Force: + return { type, "force"_liv, ArgTypeCategory::CopyFlagToSubContext }; + + case Execution::Args::Type::DependencySource: + return { type, "dependency-source"_liv, ArgTypeCategory::Source }; + case Execution::Args::Type::CustomHeader: + return { type, "header"_liv, ArgTypeCategory::Source }; + case Execution::Args::Type::AcceptSourceAgreements: + return { type, "accept-source-agreements"_liv, ArgTypeCategory::Source }; + + case Execution::Args::Type::ToolVersion: + return { type, "version"_liv, 'v' }; + + // Used for demonstration purposes + case Execution::Args::Type::ExperimentalArg: + return { type, "arg"_liv }; + + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector ArgumentCommon::GetFromExecArgs(const Execution::Args& execArgs) + { + auto argTypes = execArgs.GetTypes(); + std::vector result; + std::transform(argTypes.begin(), argTypes.end(), std::back_inserter(result), ArgumentCommon::ForType); + return result; + } + Argument Argument::ForType(Execution::Args::Type type) { switch (type) { case Args::Type::Query: - return Argument{ "query"_liv, 'q', Args::Type::Query, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; + return Argument{ type, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; case Args::Type::Manifest: - return Argument{ "manifest"_liv, 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::AdminSetting::LocalManifestFiles }; + return Argument{ type, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::AdminSetting::LocalManifestFiles }; case Args::Type::Id: - return Argument{ "id"_liv, NoAlias, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Name: - return Argument{ "name"_liv, NoAlias, Args::Type::Name, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Moniker: - return Argument{ "moniker"_liv, NoAlias, Args::Type::Moniker, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Tag: - return Argument{ "tag"_liv, NoAlias, Args::Type::Tag, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Command: - return Argument{ "command"_liv, NoAlias, "cmd"_liv, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Source: - return Argument{ "source"_liv, 's', Args::Type::Source, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; case Args::Type::DependencySource: - return Argument{ "dependency-source"_liv, NoAlias, Args::Type::DependencySource, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; case Args::Type::Count: - return Argument{ "count"_liv, 'n', Args::Type::Count, Resource::String::CountArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::CountArgumentDescription, ArgumentType::Standard }; case Args::Type::Exact: - return Argument{ "exact"_liv, 'e', Args::Type::Exact, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; case Args::Type::Version: - return Argument{ "version"_liv, 'v', Args::Type::Version, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; case Args::Type::Channel: - return Argument{ "channel"_liv, 'c', Args::Type::Channel, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; + return Argument{ type, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; case Args::Type::Interactive: - return Argument{ "interactive"_liv, 'i', Args::Type::Interactive, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; case Args::Type::Silent: - return Argument{ "silent"_liv, 'h', Args::Type::Silent, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; case Args::Type::Locale: - return Argument{ "locale"_liv, NoAlias, Args::Type::Locale, Resource::String::LocaleArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::LocaleArgumentDescription, ArgumentType::Standard }; case Args::Type::InstallArchitecture: - return Argument{ "architecture"_liv, 'a', Args::Type::InstallArchitecture, Resource::String::InstallArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::InstallArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Log: - return Argument{ "log"_liv, 'o', Args::Type::Log, Resource::String::LogArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::LogArgumentDescription, ArgumentType::Standard }; case Args::Type::CustomSwitches: - return Argument{ "custom"_liv, NoAlias, Args::Type::CustomSwitches, Resource::String::CustomSwitchesArgumentDescription, ArgumentType::Standard}; + return Argument{ type, Resource::String::CustomSwitchesArgumentDescription, ArgumentType::Standard }; case Args::Type::Override: - return Argument{ "override"_liv, NoAlias, Args::Type::Override, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::InstallLocation: - return Argument{ "location"_liv, 'l', Args::Type::InstallLocation, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; + return Argument{ type, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; case Args::Type::HashOverride: - return Argument{ "ignore-security-hash"_liv, NoAlias, Args::Type::HashOverride, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride, Settings::AdminSetting::InstallerHashOverride }; + return Argument{ type, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride, Settings::AdminSetting::InstallerHashOverride }; case Args::Type::AcceptPackageAgreements: - return Argument{ "accept-package-agreements"_liv, NoAlias, Args::Type::AcceptPackageAgreements, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag }; case Args::Type::NoUpgrade: - return Argument{ "no-upgrade"_liv, NoAlias, Args::Type::NoUpgrade, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag }; case Args::Type::HashFile: - return Argument{ "file"_liv, 'f', Args::Type::HashFile, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; + return Argument{ type, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; case Args::Type::Msix: - return Argument{ "msix"_liv, 'm', Args::Type::Msix, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; case Args::Type::ListVersions: - return Argument{ "versions"_liv, NoAlias, Args::Type::ListVersions, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; case Args::Type::Help: - return Argument{ "help"_liv, APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR, Args::Type::Help, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; case Args::Type::IgnoreLocalArchiveMalwareScan: - return Argument{ "ignore-local-archive-malware-scan"_liv, NoAlias, Args::Type::IgnoreLocalArchiveMalwareScan, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::AdminSetting::LocalArchiveMalwareScanOverride }; + return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::AdminSetting::LocalArchiveMalwareScanOverride }; case Args::Type::SourceName: - return Argument{ "name"_liv, 'n', Args::Type::SourceName,Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; + return Argument{ type, Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; case Args::Type::SourceArg: - return Argument{ "arg"_liv, 'a', Args::Type::SourceArg, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; + return Argument{ type, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; case Args::Type::SourceType: - return Argument{ "type"_liv, 't', Args::Type::SourceType, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; + return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; case Args::Type::ValidateManifest: - return Argument{ "manifest"_liv, NoAlias, Args::Type::ValidateManifest, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; + return Argument{ type, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; case Args::Type::NoVT: - return Argument{ "no-vt"_liv, NoAlias, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + return Argument{ type, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::RainbowStyle: - return Argument{ "rainbow"_liv, NoAlias, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + return Argument{ type, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::RetroStyle: - return Argument{ "retro"_liv, NoAlias, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + return Argument{ type, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::VerboseLogs: - return Argument{ "verbose-logs"_liv, NoAlias, "verbose"_liv, Args::Type::VerboseLogs, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag}; + return Argument{ type, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; case Args::Type::CustomHeader: - return Argument{ "header"_liv, NoAlias, Args::Type::CustomHeader, Resource::String::HeaderArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ type, Resource::String::HeaderArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::AcceptSourceAgreements: - return Argument{ "accept-source-agreements"_liv, NoAlias, Args::Type::AcceptSourceAgreements, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; + return Argument{ type, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; case Args::Type::ExperimentalArg: - return Argument{ "arg"_liv, NoAlias, Args::Type::ExperimentalArg, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; + return Argument{ type, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; case Args::Type::Rename: - return Argument{ "rename"_liv, 'r', Args::Type::Rename, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false }; + return Argument{ type, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false }; case Args::Type::Purge: - return Argument{ "purge"_liv, NoAlias, Args::Type::Purge, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; + return Argument{ type, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; case Args::Type::Preserve: - return Argument{ "preserve"_liv, NoAlias, Args::Type::Preserve, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; + return Argument{ type, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; case Args::Type::Wait: - return Argument{ "wait"_liv, NoAlias, Args::Type::Wait, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; + return Argument{ type, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; case Args::Type::ProductCode: - return Argument{ "product-code"_liv, NoAlias, Args::Type::ProductCode, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false }; + return Argument{ type, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false }; case Args::Type::OpenLogs: - return Argument{ "open-logs"_liv, NoAlias, "logs"_liv, Args::Type::OpenLogs, Resource::String::OpenLogsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help}; + return Argument{ type, Resource::String::OpenLogsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; case Args::Type::UninstallPrevious: - return Argument{ "uninstall-previous"_liv, NoAlias, Args::Type::UninstallPrevious, Resource::String::UninstallPreviousArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::UninstallPreviousArgument }; + return Argument{ type, Resource::String::UninstallPreviousArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::UninstallPreviousArgument }; case Args::Type::Force: - return Argument{ "force"_liv, NoAlias, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag, false }; + return Argument{ type, Resource::String::ForceArgumentDescription, ArgumentType::Flag, false }; default: THROW_HR(E_UNEXPECTED); } @@ -127,35 +323,99 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::RainbowStyle)); args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); - args.emplace_back("disable-interactivity", NoAlias, Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); + args.emplace_back(Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); } std::string Argument::GetUsageString() const { std::ostringstream strstr; - if (m_alias != Argument::NoAlias) + if (Alias() != ArgumentCommon::NoAlias) { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << m_alias << ','; + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Alias() << ','; } - if (m_alternateName != Argument::NoAlternateName) + if (AlternateName() != Argument::NoAlternateName) { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << m_alternateName << ','; + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << AlternateName() << ','; } - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << m_name; + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Name(); return strstr.str(); } - void Argument::ValidatePackageSelectionArgumentSupplied(const Execution::Args& args) + void Argument::ValidateExclusiveArguments(const Execution::Args& args) + { + auto argProperties = ArgumentCommon::GetFromExecArgs(args); + + using ExclusiveSet_t = std::underlying_type_t; + for (ExclusiveSet_t i = 1 + static_cast(ArgTypeExclusiveSet::None); i < static_cast(ArgTypeExclusiveSet::Max); i <<= 1) + { + std::vector argsFromSet; + std::copy_if( + argProperties.begin(), + argProperties.end(), + std::back_inserter(argsFromSet), + [=](const ArgumentCommon& arg) { return static_cast(arg.ExclusiveSet) & i; }); + + if (argsFromSet.size() > 1) + { + // Create a string showing the exclusive args. + std::string argsString; + for (const auto& arg : argsFromSet) + { + if (!argsString.empty()) + { + argsString += '|'; + + } + + argsString += arg.Name; + } + + throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided(Utility::LocIndString{ argsString })); + } + } + } + + ArgTypeCategory Argument::GetCategoriesPresent(const Execution::Args& args) { - for (Args::Type type : { Args::Type::Query, Args::Type::Manifest, Args::Type::Id, Args::Type::Name, Args::Type::Moniker, Args::Type::ProductCode, Args::Type::Tag, Args::Type::Command }) + auto argProperties = ArgumentCommon::GetFromExecArgs(args); + + ArgTypeCategory result = ArgTypeCategory::None; + for (const auto& arg : argProperties) { - if (args.Contains(type)) + result |= arg.TypeCategory; + } + + return result; + } + + ArgTypeCategory Argument::GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg) + { + const auto categories = GetCategoriesPresent(args); + + // Commands like install require some argument to select a package + if (requirePackageSelectionArg) + { + if (WI_AreAllFlagsClear(categories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery)) { - return; + throw CommandException(Resource::String::NoPackageSelectionArgumentProvided); } } - throw CommandException(Resource::String::NoPackageSelectionArgumentProvided); + // If a manifest is specified, we cannot also have arguments for searching + if (WI_IsFlagSet(categories, ArgTypeCategory::Manifest) && + WI_IsAnyFlagSet(categories, ArgTypeCategory::PackageQuery | ArgTypeCategory::Source)) + { + throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); + } + + // If we have multiple packages, we cannot have arguments that only make sense for a single package + if (WI_IsFlagSet(categories, ArgTypeCategory::MultiplePackages) && + WI_IsAnyFlagSet(categories, ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::SingleInstallerBehavior)) + { + throw CommandException(Resource::String::ArgumentForSinglePackageProvidedWithMultipleQueries); + } + + return categories; } Argument::Visibility Argument::GetVisibility() const diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h index 480a8b0d9d..93d41cd4d1 100644 --- a/src/AppInstallerCLICore/Argument.h +++ b/src/AppInstallerCLICore/Argument.h @@ -23,7 +23,6 @@ namespace AppInstaller::CLI { using namespace AppInstaller::Utility::literals; - constexpr Utility::LocIndView s_ArgumentName_Scope = "scope"_liv; // The type of argument. enum class ArgumentType @@ -36,6 +35,90 @@ namespace AppInstaller::CLI Flag, }; + // Categories an arg type can belong to. + // Used to reason about the arguments present without having to repeat the same + // lists every time. + enum class ArgTypeCategory + { + None = 0, + // The --manifest argument. + Manifest = 0x1, + // Arguments for querying or selecting a package. + // E.g.: --query + PackageQuery = 0x2, + // Arguments for querying or selecting a package, which do not work for multiple packages. + // E.g.: --version + SinglePackageQuery = 0x4, + // Arguments for installer or uninstaller selection. + // E.g.: --scope + InstallerSelection = 0x8, + // Arguments for installer or uninstaller behavior. + // E.g.: --interactive + InstallerBehavior = 0x10, + // Arguments for installer or uninstaller behavior, which do not work for multiple packages. + // E.g.: --override + SingleInstallerBehavior = 0x20, + // Arguments for selecting or interacting with the source. + // E.g.: --accept-source-agreements + Source = 0x40, + // Arguments that only make sense when talking about multiple packages + MultiplePackages = 0x80, + // Flag arguments that should be copied over when creating a sub-context + CopyFlagToSubContext = 0x100, + // Arguments with associated values that should be copied over when creating a sub-context + CopyValueToSubContext = 0x200, + }; + + DEFINE_ENUM_FLAG_OPERATORS(ArgTypeCategory); + + // Exclusive sets an argument can belong to. + // Only one argument from each exclusive set is allowed at a time. + enum class ArgTypeExclusiveSet : uint32_t + { + None = 0x0, + ProgressBarOption = 0x1, + EnableDisable = 0x2, + PurgePreserve = 0x4, + PinType = 0x8, + + // This must always be at the end + Max + }; + + DEFINE_ENUM_FLAG_OPERATORS(ArgTypeExclusiveSet); + + // An argument to a command; containing only data that is common to all its uses. + // Argument extends this by adding command-specific values, like help strings. + struct ArgumentCommon + { + // Defines an argument with no alias. + constexpr static char NoAlias = '\0'; + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, char alias, Utility::LocIndView alternateName, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(alias), AlternateName(alternateName), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, char alias, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(alias), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, Utility::LocIndView alternateName, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(NoAlias), AlternateName(alternateName), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(NoAlias), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + // Gets the argument for the given type. + static ArgumentCommon ForType(Execution::Args::Type execArgType); + + static std::vector GetFromExecArgs(const Execution::Args& execArgs); + + Execution::Args::Type Type; + Utility::LocIndView Name; + char Alias; + Utility::LocIndView AlternateName; + ArgTypeCategory TypeCategory; + ArgTypeExclusiveSet ExclusiveSet; + }; + // An argument to a command. struct Argument { @@ -50,47 +133,35 @@ namespace AppInstaller::CLI Hidden, }; - // Defines an argument with no alias. - constexpr static char NoAlias = '\0'; - // Defines an argument with no alternate name constexpr static std::string_view NoAlternateName = ""; - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, bool required) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, bool required) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_required(required) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_required(required) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, bool required) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required) {} +#ifndef AICLI_DISABLE_TEST_HOOKS + // Constructors for arguments with custom names and aliases to use in tests + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : + m_argCommon(execArgType, Utility::LocIndView{ name }, alias), m_desc(std::move(desc)), m_type(type) {} Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type) {} - - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility) {} - - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required) {} - - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required) {} + m_argCommon(execArgType, Utility::LocIndView{ name }, alias, Utility::LocIndView{ alternateName }), m_desc(std::move(desc)), m_type(type) {} +#endif ~Argument() = default; @@ -108,17 +179,23 @@ namespace AppInstaller::CLI // Static argument validation helpers; throw CommandException when validation fails. - // Requires that some form of package selection argument is present - static void ValidatePackageSelectionArgumentSupplied(const Execution::Args& args); + // Requires that at most one argument from the list is present. + static void ValidateExclusiveArguments(const Execution::Args& args); + + static ArgTypeCategory GetCategoriesPresent(const Execution::Args& arg); + + // Requires that arguments meet common requirements + static ArgTypeCategory GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg = true); + static void ValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg = true) { std::ignore = GetCategoriesAndValidateCommonArguments(args, requirePackageSelectionArg); } // Gets the argument usage string in the format of "-alias,--name". std::string GetUsageString() const; // Arguments are not localized at this time. - Utility::LocIndView Name() const { return Utility::LocIndView{ m_name }; } - char Alias() const { return m_alias; } - std::string_view AlternateName() const { return m_alternateName; } - Execution::Args::Type ExecArgType() const { return m_execArgType; } + Utility::LocIndView Name() const { return m_argCommon.Name; } + char Alias() const { return m_argCommon.Alias; } + std::string_view AlternateName() const { return m_argCommon.AlternateName; } + Execution::Args::Type ExecArgType() const { return m_argCommon.Type; } const Resource::StringId& Description() const { return m_desc; } bool Required() const { return m_required; } ArgumentType Type() const { return m_type; } @@ -133,58 +210,31 @@ namespace AppInstaller::CLI private: // Constructors that set a Feature or Policy are private to force callers to go through the ForType() function. // This helps keep it all in one place to reduce chances of missing it somewhere. - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_feature(feature) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, bool required, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required), m_feature(feature) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_feature(feature) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_feature(feature) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required), m_feature(feature) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required), m_feature(feature) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_feature(feature) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_feature(feature) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, bool required, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required), m_feature(feature) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, bool required, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_required(required), m_feature(feature) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_feature(feature) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_feature(feature) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_feature(feature) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_feature(feature) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required), m_feature(feature) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_required(required), m_feature(feature) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required, Settings::ExperimentalFeature::Feature feature) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required), m_feature(feature) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required), m_feature(feature) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : - m_name(name), m_alias(alias), m_alternateName(alternateName), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - std::string_view m_name; - char m_alias; - std::string_view m_alternateName; - Execution::Args::Type m_execArgType; + ArgumentCommon m_argCommon; Resource::StringId m_desc; bool m_required = false; ArgumentType m_type = ArgumentType::Standard; diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp index deb8334dab..03e2fe4b8a 100644 --- a/src/AppInstallerCLICore/Command.cpp +++ b/src/AppInstallerCLICore/Command.cpp @@ -124,7 +124,7 @@ namespace AppInstaller::CLI infoOut << '['; - if (arg.Alias() == Argument::NoAlias) + if (arg.Alias() == ArgumentCommon::NoAlias) { infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); } @@ -704,10 +704,12 @@ namespace AppInstaller::CLI if (Manifest::ConvertToScopeEnum(execArgs.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Unknown) { auto validOptions = Utility::Join(", "_liv, std::vector{ "user"_lis, "machine"_lis}); - throw CommandException(Resource::String::InvalidArgumentValueError(s_ArgumentName_Scope, validOptions)); + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::InstallScope).Name, validOptions)); } } + Argument::ValidateExclusiveArguments(execArgs); + ValidateArgumentsInternal(execArgs); } @@ -780,7 +782,7 @@ namespace AppInstaller::CLI { for (const auto& arg : stateMachine.Arguments()) { - if (arg.Alias() != Argument::NoAlias) + if (arg.Alias() != ArgumentCommon::NoAlias) { context.Reporter.Completion() << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << std::endl; } diff --git a/src/AppInstallerCLICore/Commands/CompleteCommand.cpp b/src/AppInstallerCLICore/Commands/CompleteCommand.cpp index 9ed6fb694f..c364cc933b 100644 --- a/src/AppInstallerCLICore/Commands/CompleteCommand.cpp +++ b/src/AppInstallerCLICore/Commands/CompleteCommand.cpp @@ -13,9 +13,9 @@ namespace AppInstaller::CLI std::vector CompleteCommand::GetArguments() const { return { - Argument{ "word", Argument::NoAlias, Args::Type::Word, Resource::String::WordArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, - Argument{ "commandline", Argument::NoAlias, Args::Type::CommandLine, Resource::String::CommandLineArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, - Argument{ "position", Argument::NoAlias, Args::Type::Position, Resource::String::PositionArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, + Argument{ Args::Type::Word, Resource::String::WordArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, + Argument{ Args::Type::CommandLine, Resource::String::CommandLineArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, + Argument{ Args::Type::Position, Resource::String::PositionArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, }; } diff --git a/src/AppInstallerCLICore/Commands/ExportCommand.cpp b/src/AppInstallerCLICore/Commands/ExportCommand.cpp index bcb33d1738..8244b71a6b 100644 --- a/src/AppInstallerCLICore/Commands/ExportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ExportCommand.cpp @@ -14,9 +14,9 @@ namespace AppInstaller::CLI std::vector ExportCommand::GetArguments() const { return { - Argument{ "output", 'o', Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Positional, true }, - Argument{ "source", 's', Execution::Args::Type::Source, Resource::String::ExportSourceArgumentDescription, ArgumentType::Standard }, - Argument{ "include-versions", Argument::NoAlias, Execution::Args::Type::IncludeVersions, Resource::String::ExportIncludeVersionsArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::Source, Resource::String::ExportSourceArgumentDescription, ArgumentType::Standard }, + Argument{ Execution::Args::Type::IncludeVersions, Resource::String::ExportIncludeVersionsArgumentDescription, ArgumentType::Flag }, Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), }; } diff --git a/src/AppInstallerCLICore/Commands/ImportCommand.cpp b/src/AppInstallerCLICore/Commands/ImportCommand.cpp index 15084185b6..a2ce2d215f 100644 --- a/src/AppInstallerCLICore/Commands/ImportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ImportCommand.cpp @@ -14,9 +14,9 @@ namespace AppInstaller::CLI std::vector ImportCommand::GetArguments() const { return { - Argument{ "import-file", 'i', Execution::Args::Type::ImportFile, Resource::String::ImportFileArgumentDescription, ArgumentType::Positional, true }, - Argument{ "ignore-unavailable", Argument::NoAlias, Execution::Args::Type::IgnoreUnavailable, Resource::String::ImportIgnoreUnavailableArgumentDescription, ArgumentType::Flag }, - Argument{ "ignore-versions", Argument::NoAlias, Execution::Args::Type::IgnoreVersions, Resource::String::ImportIgnorePackageVersionsArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::ImportFile, Resource::String::ImportFileArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::IgnoreUnavailable, Resource::String::ImportIgnoreUnavailableArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::IgnoreVersions, Resource::String::ImportIgnorePackageVersionsArgumentDescription, ArgumentType::Flag }, Argument::ForType(Execution::Args::Type::NoUpgrade), Argument::ForType(Execution::Args::Type::AcceptPackageAgreements), Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 816e0e545c..858c8aa2a4 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -26,7 +26,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Version), Argument::ForType(Args::Type::Channel), Argument::ForType(Args::Type::Source), - Argument{ s_ArgumentName_Scope, Argument::NoAlias, Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Args::Type::InstallArchitecture), Argument::ForType(Args::Type::Exact), Argument::ForType(Args::Type::Interactive), @@ -95,21 +95,7 @@ namespace AppInstaller::CLI void InstallCommand::ValidateArgumentsInternal(Args& execArgs) const { - Argument::ValidatePackageSelectionArgumentSupplied(execArgs); - - if (execArgs.Contains(Args::Type::Manifest) && - (execArgs.Contains(Args::Type::Query) || - execArgs.Contains(Args::Type::Id) || - execArgs.Contains(Args::Type::Name) || - execArgs.Contains(Args::Type::Moniker) || - execArgs.Contains(Args::Type::Version) || - execArgs.Contains(Args::Type::Channel) || - execArgs.Contains(Args::Type::Source) || - execArgs.Contains(Args::Type::Exact))) - { - throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); - } - + Argument::ValidateCommonArguments(execArgs); } void InstallCommand::ExecuteInternal(Context& context) const diff --git a/src/AppInstallerCLICore/Commands/ListCommand.cpp b/src/AppInstallerCLICore/Commands/ListCommand.cpp index 26640d2005..3b4cc12a3c 100644 --- a/src/AppInstallerCLICore/Commands/ListCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ListCommand.cpp @@ -22,7 +22,7 @@ namespace AppInstaller::CLI Argument::ForType(Execution::Args::Type::Command), Argument::ForType(Execution::Args::Type::Count), Argument::ForType(Execution::Args::Type::Exact), - Argument{ s_ArgumentName_Scope, Argument::NoAlias, Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Execution::Args::Type::CustomHeader), Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), }; diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 81a94755d1..3330702fe2 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -55,12 +55,12 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Tag), Argument::ForType(Args::Type::Command), Argument::ForType(Args::Type::Exact), - Argument{ "version"_liv, 'v', Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, + Argument{ Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, Argument::ForType(Args::Type::Source), Argument::ForType(Args::Type::CustomHeader), Argument::ForType(Args::Type::AcceptSourceAgreements), Argument::ForType(Args::Type::Force), - Argument{ "blocking"_liv, Argument::NoAlias, Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, }; } @@ -107,11 +107,7 @@ namespace AppInstaller::CLI void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { - if (execArgs.Contains(Execution::Args::Type::GatedVersion) && execArgs.Contains(Execution::Args::Type::BlockingPin)) - { - throw CommandException(Resource::String::BothGatedVersionAndBlockingFlagProvided); - } - + Argument::ValidateCommonArguments(execArgs); } void PinAddCommand::ExecuteInternal(Execution::Context& context) const diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index 6e3ba38d3a..bf66bcbb9c 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -138,8 +138,8 @@ namespace AppInstaller::CLI { return { - Argument{ "version", 'v', Execution::Args::Type::ListVersions, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ "info", Argument::NoAlias, Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ToolVersion, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, }; } @@ -211,7 +211,7 @@ namespace AppInstaller::CLI OutputGroupPolicies(context); } - else if (context.Args.Contains(Execution::Args::Type::ListVersions)) + else if (context.Args.Contains(Execution::Args::Type::ToolVersion)) { context.Reporter.Info() << 'v' << Runtime::GetClientVersion() << std::endl; } diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp index fcac27c4df..f60213a21d 100644 --- a/src/AppInstallerCLICore/Commands/SearchCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -69,7 +69,7 @@ namespace AppInstaller::CLI void SearchCommand::ValidateArgumentsInternal(Args& execArgs) const { - Argument::ValidatePackageSelectionArgumentSupplied(execArgs); + Argument::ValidateCommonArguments(execArgs); } void SearchCommand::ExecuteInternal(Context& context) const diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index f205a6f228..55c436e631 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -13,9 +13,6 @@ namespace AppInstaller::CLI namespace { - constexpr Utility::LocIndView s_ArgumentName_Enable = "enable"_liv; - constexpr Utility::LocIndView s_ArgumentName_Disable = "disable"_liv; - constexpr Utility::LocIndView s_ArgName_EnableAndDisable = "enable|disable"_liv; Utility::LocIndView s_SettingsCommand_HelpLink = "https://aka.ms/winget-settings"_liv; } @@ -29,8 +26,8 @@ namespace AppInstaller::CLI std::vector SettingsCommand::GetArguments() const { return { - Argument{ s_ArgumentName_Enable, Argument::NoAlias, Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument{ s_ArgumentName_Disable, Argument::NoAlias, Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, }; } @@ -51,11 +48,6 @@ namespace AppInstaller::CLI void SettingsCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { - if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && execArgs.Contains(Execution::Args::Type::AdminSettingDisable)) - { - throw CommandException(Resource::String::TooManyAdminSettingArgumentsError(s_ArgName_EnableAndDisable)); - } - // Get admin setting string for all available options except Unknown using AdminSetting_t = std::underlying_type_t; std::vector adminSettingList; @@ -68,12 +60,12 @@ namespace AppInstaller::CLI if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && AdminSetting::Unknown == StringToAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingEnable))) { - throw CommandException(Resource::String::InvalidArgumentValueError(s_ArgumentName_Enable, validOptions)); + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingEnable).Name, validOptions)); } if (execArgs.Contains(Execution::Args::Type::AdminSettingDisable) && AdminSetting::Unknown == StringToAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingDisable))) { - throw CommandException(Resource::String::InvalidArgumentValueError(s_ArgumentName_Disable, validOptions)); + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingDisable).Name, validOptions)); } } diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp index 981dd465b7..b8530a2476 100644 --- a/src/AppInstallerCLICore/Commands/ShowCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -16,7 +16,7 @@ namespace AppInstaller::CLI return { Argument::ForType(Execution::Args::Type::Query), // The manifest argument from Argument::ForType can be blocked by Group Policy but we don't want that here - Argument{ "manifest", 'm', Execution::Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Execution::Args::Type::Id), Argument::ForType(Execution::Args::Type::Name), Argument::ForType(Execution::Args::Type::Moniker), @@ -24,7 +24,7 @@ namespace AppInstaller::CLI Argument::ForType(Execution::Args::Type::Channel), Argument::ForType(Execution::Args::Type::Source), Argument::ForType(Execution::Args::Type::Exact), - Argument{ s_ArgumentName_Scope, Argument::NoAlias, Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Execution::Args::Type::InstallArchitecture), Argument::ForType(Execution::Args::Type::Locale), Argument::ForType(Execution::Args::Type::ListVersions), @@ -56,20 +56,7 @@ namespace AppInstaller::CLI void ShowCommand::ValidateArgumentsInternal(Args& execArgs) const { - Argument::ValidatePackageSelectionArgumentSupplied(execArgs); - - if (execArgs.Contains(Args::Type::Manifest) && - (execArgs.Contains(Args::Type::Query) || - execArgs.Contains(Args::Type::Id) || - execArgs.Contains(Args::Type::Name) || - execArgs.Contains(Args::Type::Moniker) || - execArgs.Contains(Args::Type::Version) || - execArgs.Contains(Args::Type::Channel) || - execArgs.Contains(Args::Type::Source) || - execArgs.Contains(Args::Type::Exact))) - { - throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); - } + Argument::ValidateCommonArguments(execArgs); } void ShowCommand::ExecuteInternal(Execution::Context& context) const diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp index ec0583b4e3..cd44ffaef5 100644 --- a/src/AppInstallerCLICore/Commands/SourceCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -204,7 +204,7 @@ namespace AppInstaller::CLI { return { Argument::ForType(Args::Type::SourceName), - Argument{ "force", Argument::NoAlias, Args::Type::ForceSourceReset, Resource::String::SourceResetForceArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::ForceSourceReset, Resource::String::SourceResetForceArgumentDescription, ArgumentType::Flag }, }; } diff --git a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp index 1da7c92b3f..e0a5f7c993 100644 --- a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp @@ -26,7 +26,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Channel), Argument::ForType(Args::Type::Source), Argument::ForType(Args::Type::Exact), - Argument{ s_ArgumentName_Scope, Argument::NoAlias, Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Args::Type::Interactive), Argument::ForType(Args::Type::Silent), Argument::ForType(Args::Type::Force), @@ -89,26 +89,7 @@ namespace AppInstaller::CLI void UninstallCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { - Argument::ValidatePackageSelectionArgumentSupplied(execArgs); - - if (execArgs.Contains(Execution::Args::Type::Manifest) && - (execArgs.Contains(Execution::Args::Type::Query) || - execArgs.Contains(Execution::Args::Type::Id) || - execArgs.Contains(Execution::Args::Type::Name) || - execArgs.Contains(Execution::Args::Type::Moniker) || - execArgs.Contains(Execution::Args::Type::ProductCode) || - execArgs.Contains(Execution::Args::Type::Version) || - execArgs.Contains(Execution::Args::Type::Channel) || - execArgs.Contains(Execution::Args::Type::Source) || - execArgs.Contains(Execution::Args::Type::Exact))) - { - throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); - } - - if (execArgs.Contains(Execution::Args::Type::Purge) && execArgs.Contains(Execution::Args::Type::Preserve)) - { - throw CommandException(Resource::String::BothPurgeAndPreserveFlagsProvided); - } + Argument::ValidateCommonArguments(execArgs); } void UninstallCommand::ExecuteInternal(Execution::Context& context) const diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 09e88de64f..8aa87eb5ae 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -19,66 +19,19 @@ namespace AppInstaller::CLI { namespace { - // Determines whether there are any arguments only used in search queries, - // as opposed to listing available upgrades - bool HasSearchQueryArguments(Execution::Args& execArgs) - { - // Note that this does not include Manifest (no search) or source related args (used for listing) - return execArgs.Contains(Args::Type::Query) || - execArgs.Contains(Args::Type::Id) || - execArgs.Contains(Args::Type::Name) || - execArgs.Contains(Args::Type::Moniker) || - execArgs.Contains(Args::Type::Version) || - execArgs.Contains(Args::Type::Channel) || - execArgs.Contains(Args::Type::Exact); - } - - // Determines whether there are any arguments only used when upgrading a single package, - // as opposed to upgrading multiple packages or listing all available upgrades - bool HasArgumentsForSinglePackage(Execution::Args& execArgs) - { - return HasSearchQueryArguments(execArgs) || - execArgs.Contains(Args::Type::Manifest); - } - - // Determines whether there are any arguments only used when dealing with multiple packages, - // either for upgrading or for listing available upgrades. - bool HasArgumentsForMultiplePackages(Execution::Args& execArgs) - { - return execArgs.Contains(Args::Type::All); - } - - // Determines whether there are any arguments only used as options during an upgrade, - // as opposed to listing available upgrades or selecting the packages. - bool HasArgumentsForInstallOptions(Execution::Args& execArgs) - { - return execArgs.Contains(Args::Type::Interactive) || - execArgs.Contains(Args::Type::Silent) || - execArgs.Contains(Args::Type::Log) || - execArgs.Contains(Args::Type::Override) || - execArgs.Contains(Args::Type::InstallLocation) || - execArgs.Contains(Args::Type::HashOverride) || - execArgs.Contains(Args::Type::IgnoreLocalArchiveMalwareScan) || - execArgs.Contains(Args::Type::AcceptPackageAgreements); - } - - // Determines whether there are any arguments related to the source. - bool HasArgumentsForSource(Execution::Args& execArgs) - { - return execArgs.Contains(Args::Type::Source) || - execArgs.Contains(Args::Type::CustomHeader) || - execArgs.Contains(Args::Type::AcceptSourceAgreements); - } - // Determines whether we should list available upgrades, instead // of performing an upgrade - bool ShouldListUpgrade(Execution::Args& execArgs) + bool ShouldListUpgrade(const Execution::Args& args, ArgTypeCategory argCategories = ArgTypeCategory::None) { - // Valid arguments for list are only those related to the sources and which packages to include. + if (argCategories == ArgTypeCategory::None) + { + argCategories = Argument::GetCategoriesPresent(args); + } + + // Valid arguments for list are only those related to the sources and which packages to include (e.g. --include-unknown). // Instead of checking for them, we check that there aren't any other arguments present. - return !execArgs.Contains(Args::Type::All) && - !HasArgumentsForSinglePackage(execArgs) && - !HasArgumentsForInstallOptions(execArgs); + return !args.Contains(Args::Type::All) && + WI_AreAllFlagsClear(argCategories, ArgTypeCategory::Manifest | ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::InstallerBehavior); } } @@ -101,7 +54,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::CustomSwitches), Argument::ForType(Args::Type::Override), Argument::ForType(Args::Type::InstallLocation), // -l - Argument{ s_ArgumentName_Scope, Argument::NoAlias, Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Args::Type::InstallArchitecture), // -a Argument::ForType(Args::Type::Locale), Argument::ForType(Args::Type::HashOverride), @@ -109,8 +62,8 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::AcceptPackageAgreements), Argument::ForType(Args::Type::AcceptSourceAgreements), Argument::ForType(Execution::Args::Type::CustomHeader), - Argument{ "all"_liv, 'r', "recurse"_liv, Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag }, - Argument{ "include-unknown"_liv, 'u', "unknown"_liv, Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag }, Argument::ForType(Args::Type::UninstallPrevious), Argument::ForType(Args::Type::Force), }; @@ -175,30 +128,14 @@ namespace AppInstaller::CLI void UpgradeCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { - if (execArgs.Contains(Execution::Args::Type::Manifest) && - (HasSearchQueryArguments(execArgs) || - HasArgumentsForMultiplePackages(execArgs) || - HasArgumentsForSource(execArgs))) - { - throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); - } + const auto argCategories = Argument::GetCategoriesAndValidateCommonArguments(execArgs, /* requirePackageSelectionArg */ false); - if (!ShouldListUpgrade(execArgs) - && !HasSearchQueryArguments(execArgs) - && (execArgs.Contains(Args::Type::Log) || - execArgs.Contains(Args::Type::Override) || - execArgs.Contains(Args::Type::InstallLocation) || - execArgs.Contains(Args::Type::HashOverride) || - execArgs.Contains(Args::Type::IgnoreLocalArchiveMalwareScan) || - execArgs.Contains(Args::Type::AcceptPackageAgreements))) + if (!ShouldListUpgrade(execArgs, argCategories) && + WI_IsFlagClear(argCategories, ArgTypeCategory::PackageQuery) && + WI_IsFlagSet(argCategories, ArgTypeCategory::SingleInstallerBehavior)) { throw CommandException(Resource::String::InvalidArgumentWithoutQueryError); } - - if (HasArgumentsForSinglePackage(execArgs) && HasArgumentsForMultiplePackages(execArgs)) - { - throw CommandException(Resource::String::IncompatibleArgumentsProvided); - } } void UpgradeCommand::ExecuteInternal(Execution::Context& context) const diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index fef3b7f829..a4fd379318 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -10,7 +10,7 @@ namespace AppInstaller::CLI::Execution { struct Args { - enum class Type + enum class Type : uint32_t { // Args to specify where to get app Query, // Query to be performed against index @@ -31,7 +31,6 @@ namespace AppInstaller::CLI::Execution Channel, // Install behavior - // When adding a new flag, we may need to copy it in Context::CreateSubContext() Interactive, Silent, Locale, @@ -111,8 +110,13 @@ namespace AppInstaller::CLI::Execution CustomHeader, // Optional Rest source header AcceptSourceAgreements, // Accept all source agreements + ToolVersion, + // Used for demonstration purposes ExperimentalArg, + + // This should always be at the end + Max }; bool Contains(Type arg) const { return (m_parsedArgs.count(arg) != 0); } @@ -156,17 +160,17 @@ namespace AppInstaller::CLI::Execution m_parsedArgs[arg].emplace_back(value); } - bool Empty() + bool Empty() const { return m_parsedArgs.empty(); } - size_t GetArgsCount() + size_t GetArgsCount() const { return m_parsedArgs.size(); } - std::vector GetTypes() + std::vector GetTypes() const { std::vector types; diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index b6b44464aa..7b89b61a0c 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "ExecutionContext.h" #include "COMContext.h" +#include "Argument.h" #include "winget/UserSettings.h" namespace AppInstaller::CLI::Execution @@ -134,31 +135,16 @@ namespace AppInstaller::CLI::Execution void Context::CopyArgsToSubContext(Context* subContext) { - // Copy over install behavior flags from the parent context. - for (auto flag : { - Args::Type::Interactive, - Args::Type::Silent, - Args::Type::HashOverride, - Args::Type::IgnoreLocalArchiveMalwareScan, - Args::Type::NoUpgrade, - Args::Type::Force, - }) + auto argProperties = ArgumentCommon::GetFromExecArgs(Args); + for (const auto& arg : argProperties) { - if (Args.Contains(flag)) + if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyFlagToSubContext)) { - subContext->Args.AddArg(flag); + subContext->Args.AddArg(arg.Type); } - } - - for (auto arg : { - Args::Type::Locale, - Args::Type::InstallScope, - Args::Type::InstallArchitecture, - }) - { - if (Args.Contains(arg)) + else if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyValueToSubContext)) { - subContext->Args.AddArg(arg, Args.GetArg(arg)); + subContext->Args.AddArg(arg.Type, Args.GetArg(arg.Type)); } } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 52634fdd12..6d0e71d726 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -29,6 +29,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription); WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScan); WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScanOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(ArgumentForSinglePackageProvidedWithMultipleQueries); WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments); WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases); WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); @@ -36,9 +37,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades); - WINGET_DEFINE_RESOURCE_STRINGID(BothGatedVersionAndBlockingFlagProvided); WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided); - WINGET_DEFINE_RESOURCE_STRINGID(BothPurgeAndPreserveFlagsProvided); WINGET_DEFINE_RESOURCE_STRINGID(Cancelled); WINGET_DEFINE_RESOURCE_STRINGID(ChannelArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Command); @@ -211,6 +210,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreStoreClientBlocked); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleExclusiveArgumentsProvided); WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); WINGET_DEFINE_RESOURCE_STRINGID(MultipleNonPortableNestedInstallersSpecified); WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound); @@ -412,7 +412,6 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(TooManyAdminSettingArgumentsError); WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); WINGET_DEFINE_RESOURCE_STRINGID(UnableToPurgeInstallDirectory); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 473f6f7309..be6778a314 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1224,10 +1224,6 @@ Do you agree to the terms? Disables the specific administrator setting - - Too many admin setting arguments provided: '{0}' - {Locked="{0}"} Error message displayed when the user provides too many admin setting arguments. {0} is a placeholder replaced by the admin setting arguments (e.g. 'enable|disable'). - Enabled admin setting '{0}'. {Locked="{0}"} Message displayed when the user enables an admin setting. {0} is a placeholder replaced by the setting name. @@ -1420,10 +1416,6 @@ Please specify one of them using the --source option to proceed. Portable install failed; Cleaning up... - - Both --purge and --preserve arguments are provided - {Locked="--purge"} {Locked="--preserve"} - Portable package has been modified; proceeding due to --force {Locked="--force"} @@ -1586,10 +1578,6 @@ Please specify one of them using the --source option to proceed. Reset pins - - Both version and '--blocking' arguments provided - {Locked="--blocking"} - Version to which to pin the package. The wildcard '*' can be used as the last version part @@ -1653,4 +1641,12 @@ Please specify one of them using the --source option to proceed. Unable to open pin database. Error message for when we cannot open the database containing package pins. + + An argument was provided that can only be used for single package + + + + Multiple mutually exclusive arguments provided: {0} + {Locked="{0}"} Error message shown when mutually incompatible command line arguments are used. {0} is a placeholder replaced by the arguments that cannot be specified together + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 71a9b660ea..b7fb1bed81 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -190,6 +190,7 @@ + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 21072d3f32..140f4de563 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -281,6 +281,9 @@ Source Files\CLI + + Source Files\CLI + diff --git a/src/AppInstallerCLITests/Argument.cpp b/src/AppInstallerCLITests/Argument.cpp new file mode 100644 index 0000000000..8dc23ca821 --- /dev/null +++ b/src/AppInstallerCLITests/Argument.cpp @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller::CLI; + +TEST_CASE("EnsureAllCommandsDefined", "[argument]") +{ + using Arg_t = std::underlying_type_t; + for (Arg_t i = static_cast(0); i < static_cast(Execution::Args::Type::Max); ++i) + { + REQUIRE_NOTHROW(ArgumentCommon::ForType(static_cast(i))); + } +} \ No newline at end of file diff --git a/src/AppInstallerCLITests/Command.cpp b/src/AppInstallerCLITests/Command.cpp index 3d1cfa4f2e..8705fb6c1c 100644 --- a/src/AppInstallerCLITests/Command.cpp +++ b/src/AppInstallerCLITests/Command.cpp @@ -42,7 +42,7 @@ std::string GetArgumentAlternateName(const Argument& arg) std::string GetArgumentAlias(const Argument& arg) { - if (arg.Alias() == Argument::NoAlias) + if (arg.Alias() == ArgumentCommon::NoAlias) { return {}; } diff --git a/src/AppInstallerCLITests/Completion.cpp b/src/AppInstallerCLITests/Completion.cpp index 2af3d1d223..08e9e6d742 100644 --- a/src/AppInstallerCLITests/Completion.cpp +++ b/src/AppInstallerCLITests/Completion.cpp @@ -142,7 +142,7 @@ void OutputAllArgumentAliases(Command& command, std::ostream& out, bool includeC for (const auto& a : args) { - if (a.Alias() != Argument::NoAlias) + if (a.Alias() != ArgumentCommon::NoAlias) { out << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << a.Alias() << std::endl; }