commit 2750b585e6848d4737074ab44c0dd5265f597a44 parent 2ae5b9cde06a196c0f369e95f905211527609edd Author: Ruben Guerrero <rubengu@microsoft.com> Date: Thu, 2 Jul 2020 11:16:00 -0700 Feature toggle (#460) Diffstat:
29 files changed, 671 insertions(+), 99 deletions(-)
diff --git a/doc/Settings.md b/doc/Settings.md @@ -6,7 +6,7 @@ You can configure WinGet by editing the `settings.json` file. The file can be op Settings file is located in %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json -If you are using the non-packaged winget version by building it from source code the file will %LOCALAPPDATA%\Microsoft\WinGet\Settings\settings.json +If you are using the non-packaged winget version by building it from source code the file will be located under %LOCALAPPDATA%\Microsoft\WinGet\Settings\settings.json ## Source @@ -34,7 +34,7 @@ These settings involve visual elements that are displayed by WinGet ``` "visual": { "progressBar": "accent" - } + }, ``` ### progressBar @@ -44,3 +44,16 @@ Color of the progress bar that WinGet displays when not specified by arguments. - accent (default) - retro - rainbow + +### experimentalFeatures + +In order to allow work to be done in master, and distributed to early adopters for their feedback, settings have the ability to control "experimental" features. + +Currently there are no experimental features, just sample ones. Once they get implemented this file will be updated. + +``` + "experimentalFeatures": { + "experimentalCmd": true, + "experimentalArg": false, + }, +``` diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -174,6 +174,8 @@ <ItemGroup> <ClInclude Include="Argument.h" /> <ClInclude Include="Command.h" /> + <ClInclude Include="Commands\ExperimentalCommand.h" /> + <ClInclude Include="Commands\FeaturesCommand.h" /> <ClInclude Include="Commands\HashCommand.h" /> <ClInclude Include="Commands\SearchCommand.h" /> <ClInclude Include="Commands\ShowCommand.h" /> @@ -203,6 +205,8 @@ <ItemGroup> <ClCompile Include="Argument.cpp" /> <ClCompile Include="Command.cpp" /> + <ClCompile Include="Commands\ExperimentalCommand.cpp" /> + <ClCompile Include="Commands\FeaturesCommand.cpp" /> <ClCompile Include="Commands\HashCommand.cpp" /> <ClCompile Include="Commands\SearchCommand.cpp" /> <ClCompile Include="Commands\ShowCommand.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -105,6 +105,12 @@ <ClInclude Include="Commands\SettingsCommand.h"> <Filter>Commands</Filter> </ClInclude> + <ClInclude Include="Commands\ExperimentalCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Commands\FeaturesCommand.h"> + <Filter>Commands</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -176,6 +182,12 @@ <ClCompile Include="Commands\SettingsCommand.cpp"> <Filter>Commands</Filter> </ClCompile> + <ClCompile Include="Commands\ExperimentalCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Commands\FeaturesCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -4,11 +4,12 @@ #include "pch.h" #include "Argument.h" #include "Resources.h" - +#include <winget/UserSettings.h> namespace AppInstaller::CLI { using namespace AppInstaller::CLI::Execution; + using namespace Settings; Argument Argument::ForType(Execution::Args::Type type) { @@ -17,19 +18,19 @@ namespace AppInstaller::CLI switch (type) { case Args::Type::Query: - return Argument{ "query", 'q', Args::Type::Query, Resource::String::QueryArgumentDescription, ArgumentType::Positional }; + return Argument{ "query", 'q', Args::Type::Query, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; case Args::Type::Manifest: - return Argument{ "manifest", 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "manifest", 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Id: - return Argument{ "id", None, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "id", None, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Name: - return Argument{ "name", None, Args::Type::Name, Resource::String::NameArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "name", None, Args::Type::Name, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Moniker: - return Argument{ "moniker", None, Args::Type::Moniker, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "moniker", None, Args::Type::Moniker, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Tag: - return Argument{ "tag", None, Args::Type::Tag, Resource::String::TagArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "tag", None, Args::Type::Tag, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Command: - return Argument{ "command", None, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "command", None, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Source: return Argument{ "source", 's', Args::Type::Source, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; case Args::Type::Count: @@ -39,17 +40,17 @@ namespace AppInstaller::CLI case Args::Type::Version: return Argument{ "version", 'v', Args::Type::Version, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; case Args::Type::Channel: - return Argument{ "channel", 'c', Args::Type::Channel, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Visibility::Hidden }; + return Argument{ "channel", 'c', Args::Type::Channel, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; case Args::Type::Interactive: return Argument{ "interactive", 'i', Args::Type::Interactive, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; case Args::Type::Silent: return Argument{ "silent", 'h', Args::Type::Silent, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; case Args::Type::Language: - return Argument{ "lang", 'a', Args::Type::Language, Resource::String::LanguageArgumentDescription, ArgumentType::Standard, Visibility::Hidden }; + return Argument{ "lang", 'a', Args::Type::Language, Resource::String::LanguageArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; case Args::Type::Log: return Argument{ "log", 'o', Args::Type::Log, Resource::String::LogArgumentDescription, ArgumentType::Standard }; case Args::Type::Override: - return Argument{ "override", None, Args::Type::Override, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Visibility::Help }; + return Argument{ "override", None, Args::Type::Override, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::InstallLocation: return Argument{ "location", 'l', Args::Type::InstallLocation, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; case Args::Type::HashFile: @@ -69,15 +70,17 @@ namespace AppInstaller::CLI case Args::Type::ValidateManifest: return Argument{ "manifest", None, Args::Type::ValidateManifest, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; case Args::Type::NoVT: - return Argument{ "no-vt", None, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "no-vt", None, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::RainbowStyle: - return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::RetroStyle: - return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::Force: return Argument{ "force", None, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag }; case Args::Type::VerboseLogs: return Argument{ "verbose-logs", None, Args::Type::VerboseLogs, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; + case Args::Type::ExperimentalArg: + return Argument{ "arg", None, Args::Type::ExperimentalArg, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; default: THROW_HR(E_UNEXPECTED); } @@ -91,4 +94,14 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); } + + Argument::Visibility Argument::GetVisibility() const + { + if (!ExperimentalFeature::IsEnabled(m_feature)) + { + return Argument::Visibility::Hidden; + } + + return m_visibility; + } } diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -3,6 +3,8 @@ #pragma once #include "ExecutionContext.h" #include "Resources.h" +#include <winget/UserSettings.h> +#include <winget/ExperimentalFeature.h> #include <string> #include <string_view> @@ -30,20 +32,20 @@ namespace AppInstaller::CLI Flag, }; - // Controls the visibility of the field. - enum class Visibility - { - // Shown in the example. - Example, - // Shown only in the table below the example. - Help, - // Not shown in help. - Hidden, - }; - // An argument to a command. struct Argument { + // Controls the visibility of the field. + enum class Visibility + { + // Shown in the example. + Example, + // Shown only in the table below the example. + Help, + // Not shown in help. + Hidden, + }; + 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)) {} @@ -53,15 +55,33 @@ namespace AppInstaller::CLI 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(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Visibility visibility) : + 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(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(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Visibility visibility, bool required) : + 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(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() = default; Argument(const Argument&) = default; @@ -84,7 +104,8 @@ namespace AppInstaller::CLI bool Required() const { return m_required; } ArgumentType Type() const { return m_type; } size_t Limit() const { return m_countLimit; } - Visibility Visibility() const { return m_visibility; } + Argument::Visibility GetVisibility() const; + Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } Argument& SetRequired(bool required) { m_required = required; return *this; } @@ -95,7 +116,8 @@ namespace AppInstaller::CLI Resource::StringId m_desc; bool m_required = false; ArgumentType m_type = ArgumentType::Standard; - ::AppInstaller::CLI::Visibility m_visibility = Visibility::Example; + Argument::Visibility m_visibility = Argument::Visibility::Example; size_t m_countLimit = 1; + Settings::ExperimentalFeature::Feature m_feature = Settings::ExperimentalFeature::Feature::None; }; } diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -3,14 +3,16 @@ #include "pch.h" #include "Command.h" #include "Resources.h" +#include <winget/UserSettings.h> namespace AppInstaller::CLI { using namespace std::string_view_literals; using namespace Utility::literals; + using namespace Settings; - Command::Command(std::string_view name, std::string_view parent) : - m_name(name) + Command::Command(std::string_view name, std::string_view parent, Command::Visibility visibility, ExperimentalFeature::Feature feature) : + m_name(name), m_visibility(visibility), m_feature(feature) { if (!parent.empty()) { @@ -75,8 +77,8 @@ namespace AppInstaller::CLI // Output the command preamble and command chain infoOut << Resource::String::Usage << ": winget"_liv << Utility::LocIndView{ commandChain }; - auto commands = GetCommands(); - auto arguments = GetArguments(); + auto commands = GetVisibleCommands(); + auto arguments = GetVisibleArguments(); bool hasArguments = false; bool hasOptions = false; @@ -181,20 +183,17 @@ namespace AppInstaller::CLI std::vector<std::string> argNames; size_t maxArgNameLength = 0; - for (const auto& arg : GetArguments()) + for (const auto& arg : arguments) { - if (arg.Visibility() != Visibility::Hidden) + std::ostringstream strstr; + if (arg.Alias() != APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER) { - std::ostringstream strstr; - if (arg.Alias() != APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER) - { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << ','; - } - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); - - argNames.emplace_back(strstr.str()); - maxArgNameLength = std::max(maxArgNameLength, argNames.back().length()); + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << ','; } + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); + + argNames.emplace_back(strstr.str()); + maxArgNameLength = std::max(maxArgNameLength, argNames.back().length()); } if (hasArguments) @@ -202,16 +201,13 @@ namespace AppInstaller::CLI infoOut << Resource::String::AvailableArguments << std::endl; size_t i = 0; - for (const auto& arg : GetArguments()) + for (const auto& arg : arguments) { - if (arg.Visibility() != Visibility::Hidden) + const std::string& argName = argNames[i++]; + if (arg.Type() == ArgumentType::Positional) { - const std::string& argName = argNames[i++]; - if (arg.Type() == ArgumentType::Positional) - { - size_t fillChars = (maxArgNameLength - argName.length()) + 2; - infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; - } + size_t fillChars = (maxArgNameLength - argName.length()) + 2; + infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; } } } @@ -226,16 +222,13 @@ namespace AppInstaller::CLI infoOut << Resource::String::AvailableOptions << std::endl; size_t i = 0; - for (const auto& arg : GetArguments()) + for (const auto& arg : arguments) { - if (arg.Visibility() != Visibility::Hidden) + const std::string& argName = argNames[i++]; + if (arg.Type() != ArgumentType::Positional) { - const std::string& argName = argNames[i++]; - if (arg.Type() != ArgumentType::Positional) - { - size_t fillChars = (maxArgNameLength - argName.length()) + 2; - infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; - } + size_t fillChars = (maxArgNameLength - argName.length()) + 2; + infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; } } } @@ -269,6 +262,13 @@ namespace AppInstaller::CLI { if (Utility::CaseInsensitiveEquals(*itr, command->Name())) { + if (!ExperimentalFeature::IsEnabled(command->Feature())) + { + auto feature = ExperimentalFeature::GetFeature(command->Feature()); + AICLI_LOG(CLI, Error, << "Trying to use command: " << *itr << " without enabling feature " << feature.JsonName()); + throw CommandException(Resource::String::FeatureDisabledMessage, feature.JsonName()); + } + AICLI_LOG(CLI, Info, << "Found subcommand: " << *itr); inv.consume(itr); return std::move(command); @@ -472,6 +472,13 @@ namespace AppInstaller::CLI { for (const auto& arg : GetArguments()) { + if (!ExperimentalFeature::IsEnabled(arg.Feature()) && execArgs.Contains(arg.ExecArgType())) + { + auto feature = ExperimentalFeature::GetFeature(arg.Feature()); + AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " without enabling feature " << feature.JsonName()); + throw CommandException(Resource::String::FeatureDisabledMessage, feature.JsonName()); + } + if (arg.Required() && !execArgs.Contains(arg.ExecArgType())) { throw CommandException(Resource::String::RequiredArgError, arg.Name()); @@ -489,4 +496,40 @@ namespace AppInstaller::CLI context.Reporter.Error() << Resource::String::PendingWorkError << std::endl; THROW_HR(E_NOTIMPL); } + + Command::Visibility Command::GetVisibility() const + { + if (!ExperimentalFeature::IsEnabled(m_feature)) + { + return Command::Visibility::Hidden; + } + + return m_visibility; + } + + std::vector<std::unique_ptr<Command>> Command::GetVisibleCommands() const + { + auto commands = GetCommands(); + + commands.erase( + std::remove_if( + commands.begin(), commands.end(), + [](const std::unique_ptr<Command>& command) { return command->GetVisibility() == Command::Visibility::Hidden; }), + commands.end()); + + return commands; + } + + std::vector<Argument> Command::GetVisibleArguments() const + { + auto arguments = GetArguments(); + + arguments.erase( + std::remove_if( + arguments.begin(), arguments.end(), + [](const Argument& arg) { return arg.GetVisibility() == Argument::Visibility::Hidden; }), + arguments.end()); + + return arguments; + } } diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h @@ -5,6 +5,8 @@ #include "ExecutionContext.h" #include "Invocation.h" #include "Resources.h" +#include <winget/UserSettings.h> +#include <winget/ExperimentalFeature.h> #include <initializer_list> #include <memory> @@ -33,7 +35,22 @@ namespace AppInstaller::CLI struct Command { - Command(std::string_view name, std::string_view parent); + // Controls the visibility of the field. + enum class Visibility + { + // Shown in help. + Show, + // Not shown in help. + Hidden, + }; + + Command(std::string_view name, std::string_view parent) : + Command(name, parent, Settings::ExperimentalFeature::Feature::None) {} + Command(std::string_view name, std::string_view parent, Command::Visibility visibility) : + Command(name, parent, visibility, Settings::ExperimentalFeature::Feature::None) {} + Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature) : + Command(name, parent, Command::Visibility::Show, feature) {} + Command(std::string_view name, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature); virtual ~Command() = default; Command(const Command&) = default; @@ -47,9 +64,13 @@ namespace AppInstaller::CLI std::string_view Name() const { return m_name; } const std::string& FullName() const { return m_fullName; } + Command::Visibility GetVisibility() const; + Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } virtual std::vector<std::unique_ptr<Command>> GetCommands() const { return {}; } virtual std::vector<Argument> GetArguments() const { return {}; } + std::vector<std::unique_ptr<Command>> GetVisibleCommands() const; + std::vector<Argument> GetVisibleArguments() const; virtual Resource::LocString ShortDescription() const { return {}; } virtual Resource::LocString LongDescription() const { return {}; } @@ -71,6 +92,8 @@ namespace AppInstaller::CLI private: std::string_view m_name; std::string m_fullName; + Command::Visibility m_visibility; + Settings::ExperimentalFeature::Feature m_feature; }; template <typename Container> diff --git a/src/AppInstallerCLICore/Commands/ExperimentalCommand.cpp b/src/AppInstallerCLICore/Commands/ExperimentalCommand.cpp @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExperimentalCommand.h" +#include <winget/UserSettings.h> + +namespace AppInstaller::CLI +{ + using namespace Utility::literals; + using namespace AppInstaller::Settings; + + using namespace std::string_view_literals; + + std::vector<Argument> ExperimentalCommand::GetArguments() const + { + return + { + Argument::ForType(Execution::Args::Type::ExperimentalArg) + }; + } + + Resource::LocString ExperimentalCommand::ShortDescription() const + { + return { Resource::String::ExperimentalCommandShortDescription }; + } + + Resource::LocString ExperimentalCommand::LongDescription() const + { + return { Resource::String::ExperimentalCommandLongDescription }; + } + + std::string ExperimentalCommand::HelpLink() const + { + return "https://aka.ms/winget-settings"; + } + + void ExperimentalCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::ExperimentalArg)) + { + static constexpr std::string_view s_ninjaCat = R"( + -<vYT` + ')hM3d$$c + `~uydNv>>$$$^ + -T}uymkZdqUdN3mowkh_ + T06m\xrwingetxYxcky` + wDNf3omhXucVi!"YyjL + `VmhkuvrHG}VVT)~:_:! r!`-_ + `hqf-.-TH3r"` !-`r^,`-!:^)kUdE" + -jGhkyY*- 'T!YyTykzGNkxLxv- +,]Vyx!` `"kKPGHKzhHy)*>' + `_|fZV*- `*\*~~*LuVkXzXUm3GGx|x` `<}cc*` + _ryGNm]~- `_"!^**vL}ycVkh5MG}|L: !V0QMv=r|_`` + `"rumdNbfyLr>!:",_--_""",---_!*iwHNNNZxv}XqbNNNNNNNNZX3RQBQQR98QTrrr*. + `"*ThZNNNNNNNNNNNNNNNNNN9NNNNNNNNNN]VNNNbNNNNdNNNQ#BBBB80N8#@QZc}}^` + `">vuhMNNNNNNNNNNN6$gENNNNNNR9&NKyZNNNRgd53GbN0#BBB$N60bNRNNNZ3XT! + .:*]coPbNNNNNN$Q$NNNdNNN$QQ6NNNNN6QR5Xy3bNEdMMENNNbkT5NNNNN60T + `-:^)YuyX3Z0QNNdMMZdN8QBQRNNN68Q6Z55dbMqqqGqdNNNUy#Ho5NNG3y + `._=*vc0NZGKa5dEBBBBQQ8QQQ8NNNgGjjjjjx=:yNNNqgBN}` + ~NZ3kyhq9B03oycuuymdNNN$ZvvxcqU~` :zN}Q#8: + }bHk}cmZNcxxxxxxxYVhg9N3! .V3ZEm, .MZkZ@v + `rPMKyVhG*-````-L8@@@@@@#x}3yvr:` r&^ 'cddoX. + xd#N53Um9r m####BM rdGY. :PV:::,:- __` + ^NN#BZ5EQv -QBQ$N3' `=^.!>;,.` `-,"::",:r!,~ + ~q68#@@@Dx` rQ86Nd" .=;~- `x-- + `P9BBbx:` ~9NNX- _~;!' -^)*:," + rN6QZ` `KNNZ_ `"x*' -^ + yNgD5^` XNNNy >)`` + .NNg#NQa` ^qN$N}. `:r^ + ZNk#QNQQdx- !N@#RgQy,- + _Zy ,h03o= ` -!G@BNQ@#85T. + ` ` :*vxL`)L! ' +)"sv; + context.Reporter.Info() << s_ninjaCat << std::endl; + } + context.Reporter.Info() << Resource::String::ThankYou << std::endl; + } +} diff --git a/src/AppInstallerCLICore/Commands/ExperimentalCommand.h b/src/AppInstallerCLICore/Commands/ExperimentalCommand.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include <winget/UserSettings.h> + +namespace AppInstaller::CLI +{ + struct ExperimentalCommand final : public Command + { + // This command is used as an example on how experimental features can be used. + // To enable this command set ExperimentalCmd = true in the settings file. + ExperimentalCommand(std::string_view parent) : Command("experimental", parent, Settings::ExperimentalFeature::Feature::ExperimentalCmd) {} + + virtual std::vector<Argument> GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/FeaturesCommand.cpp b/src/AppInstallerCLICore/Commands/FeaturesCommand.cpp @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "FeaturesCommand.h" +#include "TableOutput.h" +#include <winget/UserSettings.h> + +namespace AppInstaller::CLI +{ + using namespace Utility::literals; + using namespace AppInstaller::Settings; + + Resource::LocString FeaturesCommand::ShortDescription() const + { + return { Resource::String::FeaturesCommandShortDescription }; + } + + Resource::LocString FeaturesCommand::LongDescription() const + { + return { Resource::String::FeaturesCommandLongDescription }; + } + + std::string FeaturesCommand::HelpLink() const + { + return "https://aka.ms/winget-experimentalfeatures"; + } + + void FeaturesCommand::ExecuteInternal(Execution::Context& context) const + { + context.Reporter.Info() << Resource::String::FeaturesMessage << std::endl << std::endl; + + auto features = ExperimentalFeature::GetAllFeatures(); + + if (!features.empty()) + { + Execution::TableOutput<4> table(context.Reporter, { "Feature", "Status", "Property", "Link" }); + for (const auto& feature : features) + { + table.OutputLine({ std::string{ feature.Name() }, ExperimentalFeature::IsEnabled(feature.GetFeature()) ? "Enabled" : "Disabled", std::string { feature.JsonName() }, std::string{ feature.Link() } }); + } + table.Complete(); + } + else + { + // Better work hard to get some out there! + context.Reporter.Info() << Resource::String::NoExperimentalFeaturesMessage << std::endl; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/FeaturesCommand.h b/src/AppInstallerCLICore/Commands/FeaturesCommand.h @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include <winget/UserSettings.h> + +namespace AppInstaller::CLI +{ + struct FeaturesCommand final : public Command + { + // This command outputs all the experimental features that are available, if they are enabled/disabled + // and a link to the spec. + FeaturesCommand(std::string_view parent) : Command("features", parent) {} + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} +#pragma once diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -10,6 +10,8 @@ #include "HashCommand.h" #include "ValidateCommand.h" #include "SettingsCommand.h" +#include "FeaturesCommand.h" +#include "ExperimentalCommand.h" #include "Resources.h" #include "TableOutput.h" @@ -28,6 +30,8 @@ namespace AppInstaller::CLI std::make_unique<HashCommand>(FullName()), std::make_unique<ValidateCommand>(FullName()), std::make_unique<SettingsCommand>(FullName()), + std::make_unique<FeaturesCommand>(FullName()), + std::make_unique<ExperimentalCommand>(FullName()), }); } @@ -35,8 +39,8 @@ namespace AppInstaller::CLI { return { - Argument{ "version", 'v', Execution::Args::Type::ListVersions, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Visibility::Help }, - Argument{ "info", APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER, Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Visibility::Help }, + Argument{ "version", 'v', Execution::Args::Type::ListVersions, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ "info", APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER, Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, }; } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -13,11 +13,6 @@ namespace AppInstaller::CLI using namespace std::string_view_literals; - std::vector<Argument> SettingsCommand::GetArguments() const - { - return {}; - } - Resource::LocString SettingsCommand::ShortDescription() const { return { Resource::String::SettingsCommandShortDescription }; diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.h b/src/AppInstallerCLICore/Commands/SettingsCommand.h @@ -9,8 +9,6 @@ namespace AppInstaller::CLI { SettingsCommand(std::string_view parent) : Command("settings", parent) {} - virtual std::vector<Argument> GetArguments() const override; - virtual Resource::LocString ShortDescription() const override; virtual Resource::LocString LongDescription() const override; diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -59,6 +59,9 @@ namespace AppInstaller::CLI::Execution Help, // Show command usage Info, // Show general info about WinGet VerboseLogs, // Increases winget logging level to verbose + + // Used for demonstration purposes + ExperimentalArg, }; bool Contains(Type arg) const { return (m_parsedArgs.count(arg) != 0); } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -39,7 +39,14 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessage); WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); @@ -75,6 +82,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); + WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Options); WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); @@ -113,6 +121,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ThankYou); WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -156,9 +156,35 @@ <data name="ExactArgumentDescription" xml:space="preserve"> <value>Find app using exact match</value> </data> + <data name="ExperimentalArgumentDescription" xml:space="preserve"> + <value>Experimental argument for demonstration purposes</value> + </data> + <data name="ExperimentalCommandLongDescription" xml:space="preserve"> + <value>This command is an example on how to implement an experimental feature. To turn on go to 'winget settings' and enable experimentalCmd or experimentalArg features.</value> + <comment>{Locked="winget settings"}</comment> + </data> + <data name="ExperimentalCommandShortDescription" xml:space="preserve"> + <value>Experimental feature example</value> + </data> <data name="ExtraPositionalError" xml:space="preserve"> <value>Found a positional argument when none was expected</value> </data> + <data name="FeatureDisabledMessage" xml:space="preserve"> + <value>This command is a work in progress, and may be changed dramatically or removed altogether in the future. To enable it, edit your settings ('winget settings') to include the experimental feature</value> + <comment>{Locked="winget settings"}</comment> + </data> + <data name="FeaturesCommandLongDescription" xml:space="preserve"> + <value>Shows the status of experimental features. Experimental features can be turn on via 'winget settings'.</value> + <comment>{Locked="winget settings"}</comment> + </data> + <data name="FeaturesCommandShortDescription" xml:space="preserve"> + <value>Shows the status of experimental features</value> + </data> + <data name="FeaturesMessage" xml:space="preserve"> + <value>The following experimental features are in progress. +They can be configured through the settings file 'winget settings'.</value> + <comment>{Locked="winget settings"}</comment> + </data> <data name="FileArgumentDescription" xml:space="preserve"> <value>File to be hashed</value> </data> @@ -266,6 +292,9 @@ <data name="NoApplicableInstallers" xml:space="preserve"> <value>No installers are applicable to the current system.</value> </data> + <data name="NoExperimentalFeaturesMessage" xml:space="preserve"> + <value>There are currently no exprimental features available. </value> + </data> <data name="NoVTArgumentDescription" xml:space="preserve"> <value>Disables VirtualTerminal display</value> <comment>{Locked="VirtualTerminal"}</comment> @@ -383,6 +412,9 @@ <data name="TagArgumentDescription" xml:space="preserve"> <value>Filter results by tag</value> </data> + <data name="ThankYou" xml:space="preserve"> + <value>Thank you for using winget</value> + </data> <data name="ThirdPartSoftwareNotices" xml:space="preserve"> <value>Third Party Notices</value> </data> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -179,6 +179,7 @@ <ItemGroup> <ClCompile Include="Command.cpp" /> <ClCompile Include="Downloader.cpp" /> + <ClCompile Include="ExperimentalFeature.cpp" /> <ClCompile Include="HashCommand.cpp" /> <ClCompile Include="MsixInfo.cpp" /> <ClCompile Include="PreIndexedPackageSource.cpp" /> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -89,6 +89,9 @@ <ClCompile Include="UserSettings.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="ExperimentalFeature.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLITests/ExperimentalFeature.cpp b/src/AppInstallerCLITests/ExperimentalFeature.cpp @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include <winget/ExperimentalFeature.h> + +#include <AppInstallerErrors.h> + +using namespace AppInstaller::Settings; + +TEST_CASE("ExperimentalFeature None", "[experimentalFeature]") +{ + // Make sure Feature::None is always enable. + REQUIRE(ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::None)); + + // Make sure to throw requesting Feature::None + REQUIRE_THROWS_HR(ExperimentalFeature::GetFeature(ExperimentalFeature::Feature::None), E_UNEXPECTED); +}+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp @@ -271,3 +271,44 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") REQUIRE(userSettingTest.GetWarnings().size() == 1); } } + +// Test one experimental feature in usersettingstest context because there's no good way to test ExperimentalFeature +TEST_CASE("SettingsExperimentalCmd", "[settings]") +{ + DeleteUserSettingsFiles(); + + SECTION("Feature off default") + { + UserSettingsTest userSettingTest; + + REQUIRE(!userSettingTest.Get<Setting::EFExperimentalCmd>()); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Feature on") + { + std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": true } })"; + SetSetting(Streams::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get<Setting::EFExperimentalCmd>()); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Feature off") + { + std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": false } })"; + SetSetting(Streams::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(!userSettingTest.Get<Setting::EFExperimentalCmd>()); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Invalid value") + { + std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": "string" } })"; + SetSetting(Streams::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(!userSettingTest.Get<Setting::EFExperimentalCmd>()); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -193,6 +193,7 @@ <ClInclude Include="Public\AppInstallerLogging.h" /> <ClInclude Include="Public\AppInstallerArchitecture.h" /> <ClInclude Include="Public\AppInstallerVersions.h" /> + <ClInclude Include="Public\winget\ExperimentalFeature.h" /> <ClInclude Include="Public\winget\ExtensionCatalog.h" /> <ClInclude Include="Public\winget\LocIndependent.h" /> <ClInclude Include="Public\winget\Settings.h" /> @@ -209,6 +210,7 @@ <ClCompile Include="Deployment.cpp" /> <ClCompile Include="Downloader.cpp" /> <ClCompile Include="Errors.cpp" /> + <ClCompile Include="ExperimentalFeature.cpp" /> <ClCompile Include="ExtensionCatalog.cpp" /> <ClCompile Include="FileLogger.cpp" /> <ClCompile Include="HttpStream\HttpClientWrapper.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -114,6 +114,9 @@ <ClInclude Include="JsonUtil.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="Public\winget\ExperimentalFeature.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -185,6 +188,9 @@ <ClCompile Include="UserSettings.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="ExperimentalFeature.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "pch.h" +#include "winget/ExperimentalFeature.h" +#include "winget/UserSettings.h" + +namespace AppInstaller::Settings +{ + bool ExperimentalFeature::IsEnabled(Feature feature) + { + switch (feature) + { + case Feature::None: + return true; + case Feature::ExperimentalCmd: + // ExperimentalArg depends on ExperimentalCmd, so instead of failing we could + // assume that if ExperimentalArg is enabled then ExperimentalCmd is as well. + return User().Get<Setting::EFExperimentalCmd>() || User().Get<Setting::EFExperimentalArg>(); + case Feature::ExperimentalArg: + return User().Get<Setting::EFExperimentalArg>(); + default: + THROW_HR(E_UNEXPECTED); + } + } + + ExperimentalFeature ExperimentalFeature::GetFeature(ExperimentalFeature::Feature feature) + { + switch (feature) + { + case Feature::ExperimentalCmd: + return ExperimentalFeature{ "Command Sample", "experimentalCmd", "https://aka.ms/winget-settings", Feature::ExperimentalCmd }; + case Feature::ExperimentalArg: + return ExperimentalFeature{ "Argument Sample", "experimentalArg", "https://aka.ms/winget-settings", Feature::ExperimentalArg }; + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector<ExperimentalFeature> ExperimentalFeature::GetAllFeatures() + { + std::vector<ExperimentalFeature> result; + + for (Feature_t i = 0x1; i < static_cast<Feature_t>(Feature::Max); i = i << 1) + { + result.emplace_back(GetFeature(static_cast<Feature>(i))); + } + + return result; + } +} diff --git a/src/AppInstallerCommonCore/JsonUtil.cpp b/src/AppInstallerCommonCore/JsonUtil.cpp @@ -9,7 +9,7 @@ namespace AppInstaller::Utility template<> std::optional<std::string> GetValue(const Json::Value& node) { - std::optional<std::string> value = std::nullopt; + std::optional<std::string> value; if (node.isString()) { @@ -22,7 +22,7 @@ namespace AppInstaller::Utility template<> std::optional<uint32_t> GetValue(const Json::Value& node) { - std::optional<uint32_t> value = std::nullopt; + std::optional<uint32_t> value; if (node.isUInt()) { @@ -32,5 +32,18 @@ namespace AppInstaller::Utility return value; } + template<> + std::optional<bool> GetValue(const Json::Value& node) + { + std::optional<bool> value; + + if (node.isBool()) + { + value = node.asBool(); + } + + return value; + } + } diff --git a/src/AppInstallerCommonCore/JsonUtil.h b/src/AppInstallerCommonCore/JsonUtil.h @@ -18,4 +18,7 @@ namespace AppInstaller::Utility template<> std::optional<uint32_t> GetValue<uint32_t>(const Json::Value& node); + template<> + std::optional<bool> GetValue<bool>(const Json::Value& node); + } diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <vector> +#include <string> +#include <type_traits> + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + + struct ExperimentalFeature + { + // To add an experimental feature + // 1 - add a flag in this enum, before Max + // 2 - add a setting in Setting enum in UserSettings.h + // 3 - follow how to add setting instructions + // 4 - provide implementation in ExperimentalFeature.cpp + enum class Feature : unsigned + { + None = 0x0, + ExperimentalCmd = 0x1, + ExperimentalArg = 0x2, + Max = 0x4, // This MUST always be last + }; + + using Feature_t = std::underlying_type_t<ExperimentalFeature::Feature>; + + ExperimentalFeature(std::string_view name, std::string_view jsonName, std::string_view link, Feature feature) : + m_name(name), m_jsonName(jsonName), m_link(link), m_feature(feature) {} + + ~ExperimentalFeature() = default; + + ExperimentalFeature(const ExperimentalFeature&) = default; + ExperimentalFeature& operator=(const ExperimentalFeature&) = default; + + ExperimentalFeature(ExperimentalFeature&&) = default; + ExperimentalFeature& operator=(ExperimentalFeature&&) = default; + + static bool IsEnabled(Feature feature); + static ExperimentalFeature GetFeature(ExperimentalFeature::Feature feature); + static std::vector<ExperimentalFeature> GetAllFeatures(); + + std::string_view Name() const { return m_name; } + std::string_view JsonName() const { return m_jsonName; } + std::string_view Link() const { return m_link; } + Feature GetFeature() const { return m_feature; } + + private: + std::string_view m_name; + std::string_view m_jsonName; + std::string_view m_link; + Feature m_feature; + }; + + inline ExperimentalFeature::Feature operator|(ExperimentalFeature::Feature lhs, ExperimentalFeature::Feature rhs) + { + return static_cast<ExperimentalFeature::Feature> ( + static_cast<ExperimentalFeature::Feature_t>(lhs) | + static_cast<ExperimentalFeature::Feature_t>(rhs)); + } + + inline ExperimentalFeature::Feature& operator|=(ExperimentalFeature::Feature& lhs, ExperimentalFeature::Feature rhs) + { + lhs = lhs | rhs; + return lhs; + } + + inline ExperimentalFeature::Feature operator&(ExperimentalFeature::Feature lhs, ExperimentalFeature::Feature rhs) + { + return static_cast<ExperimentalFeature::Feature>( + static_cast<ExperimentalFeature::Feature_t>(lhs) & + static_cast<ExperimentalFeature::Feature_t>(rhs)); + } + + inline ExperimentalFeature::Feature& operator&=(ExperimentalFeature::Feature& lhs, ExperimentalFeature::Feature rhs) + { + lhs = lhs & rhs; + return lhs; + } +} diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -38,12 +38,14 @@ namespace AppInstaller::Settings // Max must be last and unused. // How to add a setting // 1 - Add to enum. - // 2 - Implement SettingMap specialization + // 2 - Implement SettingMap specialization via SETTINGMAPPING_SPECIALIZATION // Validate will be called by ValidateAll without any more changes. enum class Setting : size_t { ProgressBarVisualStyle, AutoUpdateTimeInMinutes, + EFExperimentalCmd, + EFExperimentalArg, Max }; @@ -60,29 +62,22 @@ namespace AppInstaller::Settings // Validate - Function that does semantic validation. }; - template <> - struct SettingMapping<Setting::ProgressBarVisualStyle> - { - using json_t = std::string; - using value_t = VisualStyle; - - static constexpr value_t DefaultValue = VisualStyle::Accent; - static constexpr std::string_view Path = ".visual.progressBar"sv; - - static std::optional<value_t> Validate(const json_t& value); - }; - - template <> - struct SettingMapping<Setting::AutoUpdateTimeInMinutes> - { - using json_t = uint32_t; - using value_t = std::chrono::minutes; +#define SETTINGMAPPING_SPECIALIZATION(_setting_, _json_, _value_, _default_, _path_) \ + template <> \ + struct SettingMapping<_setting_> \ + { \ + using json_t = _json_; \ + using value_t = _value_; \ + static constexpr value_t DefaultValue = _default_; \ + static constexpr std::string_view Path = _path_; \ + static std::optional<value_t> Validate(const json_t& value); \ + } - static constexpr std::chrono::minutes DefaultValue = 5min; - static constexpr std::string_view Path = ".source.autoUpdateIntervalInMinutes"sv; + SETTINGMAPPING_SPECIALIZATION(Setting::ProgressBarVisualStyle, std::string, VisualStyle, VisualStyle::Accent, ".visual.progressBar"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::AutoUpdateTimeInMinutes, uint32_t, std::chrono::minutes, 5min, ".source.autoUpdateIntervalInMinutes"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); - static std::optional<value_t> Validate(const json_t& value); - }; // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. template <size_t... I> @@ -134,7 +129,6 @@ namespace AppInstaller::Settings private: UserSettingsType m_type = UserSettingsType::Default; std::vector<std::string> m_warnings; - std::map<Setting, details::SettingVariant> m_settings; protected: diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -19,7 +19,7 @@ namespace AppInstaller::Settings // For documentation on these settings, see: https://aka.ms/winget-settings // "source": { // "autoUpdateIntervalInMinutes": 5 - // } + // }, })"sv; namespace SettingsMessage @@ -45,7 +45,6 @@ namespace AppInstaller::Settings { std::string convertedValue; - // This won't work when there's a json_t with bool. Change when this happens. if constexpr (std::is_arithmetic_v<T>) { convertedValue = std::to_string(value); @@ -172,6 +171,18 @@ namespace AppInstaller::Settings return {}; } + + std::optional<SettingMapping<Setting::EFExperimentalCmd>::value_t> + SettingMapping<Setting::EFExperimentalCmd>::Validate(const SettingMapping<Setting::EFExperimentalCmd>::json_t& value) + { + return value; + } + + std::optional<SettingMapping<Setting::EFExperimentalArg>::value_t> + SettingMapping<Setting::EFExperimentalArg>::Validate(const SettingMapping<Setting::EFExperimentalArg>::json_t& value) + { + return value; + } } UserSettings::UserSettings() : m_type(UserSettingsType::Default)