commit c62098a12571bea5fdc2eda465f62df325bedcb1 parent 38bc61c81ef1dce51e84bed334e7c83e71c0e847 Author: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 6 Mar 2020 16:36:53 -0800 Add ExecutionContext to manage parameter passing (#47) * Execution Context * Refactor done and tests updated * PR comments Diffstat:
42 files changed, 757 insertions(+), 678 deletions(-)
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -169,12 +169,14 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="Command.h" /> - <ClInclude Include="Commands\Common.h" /> <ClInclude Include="Commands\SearchCommand.h" /> <ClInclude Include="Commands\ShowCommand.h" /> <ClInclude Include="Commands\InstallCommand.h" /> <ClInclude Include="Commands\RootCommand.h" /> <ClInclude Include="Commands\SourceCommand.h" /> + <ClInclude Include="ExecutionArgs.h" /> + <ClInclude Include="ExecutionContext.h" /> + <ClInclude Include="ExecutionReporter.h" /> <ClInclude Include="Invocation.h" /> <ClInclude Include="Localization.h" /> <ClInclude Include="pch.h" /> @@ -189,7 +191,6 @@ <ClInclude Include="Workflows\MsixInstallerHandler.h" /> <ClInclude Include="Workflows\ShowFlow.h" /> <ClInclude Include="Workflows\WorkflowBase.h" /> - <ClInclude Include="Workflows\WorkflowReporter.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="Command.cpp" /> @@ -199,6 +200,7 @@ <ClCompile Include="Commands\RootCommand.cpp" /> <ClCompile Include="Commands\SourceCommand.cpp" /> <ClCompile Include="Core.cpp" /> + <ClCompile Include="ExecutionReporter.cpp" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> @@ -210,7 +212,6 @@ <ClCompile Include="Workflows\MsixInstallerHandler.cpp" /> <ClCompile Include="Workflows\ShowFlow.cpp" /> <ClCompile Include="Workflows\WorkflowBase.cpp" /> - <ClCompile Include="Workflows\WorkflowReporter.cpp" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -54,12 +54,6 @@ <ClInclude Include="Workflows\ManifestComparator.h"> <Filter>Workflows</Filter> </ClInclude> - <ClInclude Include="Workflows\WorkflowReporter.h"> - <Filter>Workflows</Filter> - </ClInclude> - <ClInclude Include="Commands\Common.h"> - <Filter>Commands</Filter> - </ClInclude> <ClInclude Include="Workflows\Common.h"> <Filter>Workflows</Filter> </ClInclude> @@ -90,6 +84,15 @@ <ClInclude Include="Workflows\ShowFlow.h"> <Filter>Workflows</Filter> </ClInclude> + <ClInclude Include="ExecutionReporter.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ExecutionContext.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ExecutionArgs.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -110,9 +113,6 @@ <ClCompile Include="Workflows\ManifestComparator.cpp"> <Filter>Workflows</Filter> </ClCompile> - <ClCompile Include="Workflows\WorkflowReporter.cpp"> - <Filter>Workflows</Filter> - </ClCompile> <ClCompile Include="Workflows\InstallerHandlerBase.cpp"> <Filter>Workflows</Filter> </ClCompile> @@ -143,6 +143,9 @@ <ClCompile Include="Workflows\InstallFlow.cpp"> <Filter>Workflows</Filter> </ClCompile> + <ClCompile Include="ExecutionReporter.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -6,53 +6,53 @@ namespace AppInstaller::CLI { - void Command::OutputIntroHeader(std::ostream& out) const + void Command::OutputIntroHeader(ExecutionReporter& reporter) const { - out << "AppInstaller Command Line" << std::endl; - out << "Copyright (c) Microsoft Corporation" << std::endl; + reporter.ShowMsg("AppInstaller Command Line"); + reporter.ShowMsg("Copyright (c) Microsoft Corporation"); } - void Command::OutputHelp(std::ostream& out, const CommandException* exception) const + void Command::OutputHelp(ExecutionReporter& reporter, const CommandException* exception) const { - OutputIntroHeader(out); - out << std::endl; + OutputIntroHeader(reporter); + reporter.EmptyLine(); if (exception) { - out << exception->Message() << " : '" << exception->Param() << '\'' << std::endl; - out << std::endl; + reporter.ShowMsg(exception->Message() + " : '" + std::string(exception->Param()) + '\'', ExecutionReporter::Level::Error); + reporter.EmptyLine(); } for (const auto& line : GetLongDescription()) { - out << line << std::endl; + reporter.ShowMsg(line); } - out << std::endl; + reporter.EmptyLine(); auto commands = GetCommands(); if (!commands.empty()) { - out << LOCME("The following commands are available:") << std::endl; - out << std::endl; + reporter.ShowMsg(LOCME("The following commands are available:")); + reporter.EmptyLine(); for (const auto& command : commands) { - out << " " << command->Name() << std::endl; - out << " " << command->ShortDescription() << std::endl; + reporter.ShowMsg(" " + std::string(command->Name())); + reporter.ShowMsg(" " + command->ShortDescription()); } - out << std::endl; - out << LOCME("For more details on a specific command, pass it the help argument.") << " [" << APPINSTALLER_CLI_HELP_ARGUMENT << "]" << std::endl; + reporter.EmptyLine(); + reporter.ShowMsg(std::string(LOCME("For more details on a specific command, pass it the help argument.")) + " [" + APPINSTALLER_CLI_HELP_ARGUMENT + "]"); } else { - out << LOCME("The following arguments are available:") << std::endl; - out << std::endl; + reporter.ShowMsg(LOCME("The following arguments are available:")); + reporter.EmptyLine(); for (const auto& arg : GetArguments()) { - out << " " << arg.Name() << std::endl; - out << " " << arg.Description() << std::endl; + reporter.ShowMsg(" " + std::string(arg.Name())); + reporter.ShowMsg(" " + arg.Description()); } } } @@ -88,7 +88,7 @@ namespace AppInstaller::CLI throw CommandException(LOCME("Unrecognized command"), *itr); } - void Command::ParseArguments(Invocation& inv) const + void Command::ParseArguments(Invocation& inv, ExecutionArgs& execArgs) const { auto definedArgs = GetArguments(); auto positionalSearchItr = definedArgs.begin(); @@ -99,7 +99,7 @@ namespace AppInstaller::CLI { // Positional argument, find the next appropriate one if the current itr isn't one or has hit its limit. if (positionalSearchItr != definedArgs.end() && - (positionalSearchItr->Type() != ArgumentType::Positional || inv.GetCount(positionalSearchItr->Name()) == positionalSearchItr->Limit())) + (positionalSearchItr->Type() != ArgumentType::Positional || execArgs.GetCount(positionalSearchItr->ExecArgType()) == positionalSearchItr->Limit())) { for (++positionalSearchItr; positionalSearchItr != definedArgs.end() && positionalSearchItr->Type() != ArgumentType::Positional; ++positionalSearchItr); } @@ -109,7 +109,7 @@ namespace AppInstaller::CLI throw CommandException(LOCME("Found a positional argument when none was expected"), *incomingArgsItr); } - inv.AddArg(positionalSearchItr->Name(), *incomingArgsItr); + execArgs.AddArg(positionalSearchItr->ExecArgType(), *incomingArgsItr); } else { @@ -124,7 +124,7 @@ namespace AppInstaller::CLI { if (arg.Type() == ArgumentType::Flag) { - inv.AddArg(arg.Name()); + execArgs.AddArg(arg.ExecArgType()); } else { @@ -133,7 +133,7 @@ namespace AppInstaller::CLI { throw CommandException(LOCME("Argument value required, but none found"), *incomingArgsItr); } - inv.AddArg(arg.Name(), *incomingArgsItr); + execArgs.AddArg(arg.ExecArgType(), *incomingArgsItr); } argFound = true; break; @@ -142,7 +142,7 @@ namespace AppInstaller::CLI if (argName == APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING) { - inv.AddArg(APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING); + execArgs.AddArg(ExecutionArgs::Type::Help); } else if (!argFound) { @@ -152,44 +152,44 @@ namespace AppInstaller::CLI } } - void Command::ValidateArguments(Invocation& inv) const + void Command::ValidateArguments(ExecutionArgs& execArgs) const { // If help is asked for, don't bother validating anything else - if (inv.Contains(APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING)) + if (execArgs.Contains(ExecutionArgs::Type::Help)) { return; } for (const auto& arg : GetArguments()) { - if (arg.Required() && !inv.Contains(arg.Name())) + if (arg.Required() && !execArgs.Contains(arg.ExecArgType())) { throw CommandException(LOCME("Required argument not provided"), arg.Name()); } - if (arg.Limit() < inv.GetCount(arg.Name())) + if (arg.Limit() < execArgs.GetCount(arg.ExecArgType())) { throw CommandException(LOCME("Argument provided more times than allowed"), arg.Name()); } } } - void Command::Execute(Invocation& inv, std::ostream& out, std::istream& in) const + void Command::Execute(ExecutionContext& context) const { AICLI_LOG(CLI, Info, << "Executing command: " << Name()); - if (inv.Contains(APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING)) + if (context.Args.Contains(ExecutionArgs::Type::Help)) { - OutputHelp(out); + OutputHelp(context.Reporter); } else { - ExecuteInternal(inv, out, in); + ExecuteInternal(context); } } - void Command::ExecuteInternal(Invocation&, std::ostream& out, std::istream&) const + void Command::ExecuteInternal(ExecutionContext& context) const { - out << LOCME("Oops, we forgot to do this...") << std::endl; + context.Reporter.ShowMsg(LOCME("Oops, we forgot to do this..."), ExecutionReporter::Level::Error); THROW_HR(E_NOTIMPL); } } diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h @@ -9,6 +9,7 @@ #include <vector> #include "Invocation.h" +#include "ExecutionContext.h" #define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR '-' #define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING "-" @@ -42,17 +43,17 @@ namespace AppInstaller::CLI struct Argument { - Argument(std::string_view name, std::string desc) : - m_name(name), m_desc(std::move(desc)) {} + Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc) : + m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)) {} - Argument(std::string_view name, std::string desc, bool required) : - m_name(name), m_desc(std::move(desc)), m_required(required) {} + Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc, bool required) : + m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required) {} - Argument(std::string_view name, std::string desc, ArgumentType type) : - m_name(name), m_desc(std::move(desc)), m_type(type) {} + Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc, ArgumentType type) : + m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type) {} - Argument(std::string_view name, std::string desc, ArgumentType type, bool required) : - m_name(name), m_desc(std::move(desc)), m_type(type), m_required(required) {} + Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc, ArgumentType type, bool required) : + m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required) {} ~Argument() = default; @@ -63,6 +64,7 @@ namespace AppInstaller::CLI Argument& operator=(Argument&&) = default; std::string_view Name() const { return m_name; } + ExecutionArgs::Type ExecArgType() const { return m_execArgType; } std::string Description() const { return m_desc; } bool Required() const { return m_required; } ArgumentType Type() const { return m_type; } @@ -70,6 +72,7 @@ namespace AppInstaller::CLI private: std::string_view m_name; + ExecutionArgs::Type m_execArgType; std::string m_desc; bool m_required = false; ArgumentType m_type = ArgumentType::Standard; @@ -95,17 +98,17 @@ namespace AppInstaller::CLI virtual std::string ShortDescription() const { return {}; } virtual std::vector<std::string> GetLongDescription() const { return {}; } - virtual void OutputIntroHeader(std::ostream& out) const; - virtual void OutputHelp(std::ostream& out, const CommandException* exception = nullptr) const; + virtual void OutputIntroHeader(ExecutionReporter& reporter) const; + virtual void OutputHelp(ExecutionReporter& reporter, const CommandException* exception = nullptr) const; virtual std::unique_ptr<Command> FindInvokedCommand(Invocation& inv) const; - virtual void ParseArguments(Invocation& inv) const; - virtual void ValidateArguments(Invocation& inv) const; + virtual void ParseArguments(Invocation& inv, ExecutionArgs& execArgs) const; + virtual void ValidateArguments(ExecutionArgs& execArgs) const; - virtual void Execute(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void Execute(ExecutionContext& context) const; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const; private: std::string_view m_name; diff --git a/src/AppInstallerCLICore/Commands/Common.h b/src/AppInstallerCLICore/Commands/Common.h @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "pch.h" -using namespace std::string_view_literals; - -namespace AppInstaller::CLI -{ - static constexpr std::string_view ARG_APPLICATION = "application"sv; - static constexpr std::string_view ARG_MANIFEST = "manifest"sv; - static constexpr std::string_view ARG_INTERACTIVE = "interactive"sv; - static constexpr std::string_view ARG_SILENT = "silent"sv; - static constexpr std::string_view ARG_LANGUAGE = "language"sv; - static constexpr std::string_view ARG_LOG = "log"sv; - static constexpr std::string_view ARG_OVERRIDE = "override"sv; - static constexpr std::string_view ARG_INSTALLLOCATION = "installlocation"sv; - static constexpr std::string_view ARG_QUERY = "query"sv; - static constexpr std::string_view ARG_ID = "id"sv; - static constexpr std::string_view ARG_NAME = "name"sv; - static constexpr std::string_view ARG_MONIKER = "moniker"sv; - static constexpr std::string_view ARG_TAG = "tag"sv; - static constexpr std::string_view ARG_COMMAND = "command"sv; - static constexpr std::string_view ARG_SOURCE = "source"sv; - static constexpr std::string_view ARG_COUNT = "count"sv; - static constexpr std::string_view ARG_EXACT = "exact"sv; - static constexpr std::string_view ARG_VERSION = "version"sv; - static constexpr std::string_view ARG_CHANNEL = "channel"sv; - static constexpr std::string_view ARG_LISTVERSIONS = "listversions"sv; -}- \ No newline at end of file diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "Common.h" #include "InstallCommand.h" #include "Localization.h" #include "Manifest\Manifest.h" @@ -12,16 +11,40 @@ using namespace AppInstaller::Workflow; namespace AppInstaller::CLI { + using namespace std::string_view_literals; + + constexpr std::string_view s_InstallCommand_ArgName_Query = "query"sv; + constexpr std::string_view s_InstallCommand_ArgName_Manifest = "manifest"sv; + constexpr std::string_view s_InstallCommand_ArgName_Id = "id"sv; + constexpr std::string_view s_InstallCommand_ArgName_Name = "name"sv; + constexpr std::string_view s_InstallCommand_ArgName_Moniker = "moniker"sv; + constexpr std::string_view s_InstallCommand_ArgName_Version = "version"sv; + constexpr std::string_view s_InstallCommand_ArgName_Channel = "channel"sv; + constexpr std::string_view s_InstallCommand_ArgName_Source = "source"sv; + constexpr std::string_view s_InstallCommand_ArgName_Exact = "exact"sv; + constexpr std::string_view s_InstallCommand_ArgName_Interactive = "interactive"sv; + constexpr std::string_view s_InstallCommand_ArgName_Silent = "silent"sv; + constexpr std::string_view s_InstallCommand_ArgName_Language = "language"sv; + constexpr std::string_view s_InstallCommand_ArgName_Log = "log"sv; + constexpr std::string_view s_InstallCommand_ArgName_Override = "override"sv; + std::vector<Argument> InstallCommand::GetArguments() const { return { - Argument{ ARG_QUERY, LOCME("The name of the application to install"), ArgumentType::Positional, false }, - Argument{ ARG_MANIFEST, LOCME("The path to the manifest of the application to install"), ArgumentType::Standard, false }, - Argument{ ARG_INTERACTIVE, LOCME("The application installation is interactive. User input is needed."), ArgumentType::Flag, false }, - Argument{ ARG_SILENT, LOCME("The application installation is silent."), ArgumentType::Flag, false }, - Argument{ ARG_LANGUAGE, LOCME("Preferred language if application installation supports multiple languages."), ArgumentType::Standard, false }, - Argument{ ARG_LOG, LOCME("Preferred log location if application installation supports custom log path."), ArgumentType::Standard, false }, - Argument{ ARG_OVERRIDE, LOCME("Override switches to be passed on to application installer."), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Query, ExecutionArgs::Type::Query, LOCME("The name of the application to install"), ArgumentType::Positional, false }, + Argument{ s_InstallCommand_ArgName_Manifest, ExecutionArgs::Type::Manifest, LOCME("The path to the manifest of the application to install"), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Id, ExecutionArgs::Type::Id, LOCME("The id of the application to show info"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Name, ExecutionArgs::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Moniker, ExecutionArgs::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Version, ExecutionArgs::Type::Version, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Channel, ExecutionArgs::Type::Channel, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Source, ExecutionArgs::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Exact, ExecutionArgs::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ s_InstallCommand_ArgName_Interactive, ExecutionArgs::Type::Interactive, LOCME("The application installation is interactive. User input is needed."), ArgumentType::Flag, false }, + Argument{ s_InstallCommand_ArgName_Silent, ExecutionArgs::Type::Silent, LOCME("The application installation is silent."), ArgumentType::Flag, false }, + Argument{ s_InstallCommand_ArgName_Language, ExecutionArgs::Type::Language, LOCME("Preferred language if application installation supports multiple languages."), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Log, ExecutionArgs::Type::Log, LOCME("Preferred log location if application installation supports custom log path."), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Override, ExecutionArgs::Type::Override, LOCME("Override switches to be passed on to application installer."), ArgumentType::Standard, false }, }; } @@ -37,25 +60,25 @@ namespace AppInstaller::CLI }; } - void InstallCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + void InstallCommand::ExecuteInternal(ExecutionContext& context) const { - InstallFlow appInstall(inv, out, in); + InstallFlow appInstall(context); appInstall.Execute(); } - void InstallCommand::ValidateArguments(Invocation& inv) const + void InstallCommand::ValidateArguments(ExecutionArgs& execArgs) const { - Command::ValidateArguments(inv); + Command::ValidateArguments(execArgs); - if (!inv.Contains(ARG_QUERY) && !inv.Contains(ARG_MANIFEST)) + if (!execArgs.Contains(ExecutionArgs::Type::Query) && !execArgs.Contains(ExecutionArgs::Type::Manifest)) { - throw CommandException(LOCME("Required argument not provided"), ARG_QUERY); + throw CommandException(LOCME("Required argument not provided"), s_InstallCommand_ArgName_Query); } - if (inv.Contains(ARG_SILENT) && inv.Contains(ARG_INTERACTIVE)) + if (execArgs.Contains(ExecutionArgs::Type::Silent) && execArgs.Contains(ExecutionArgs::Type::Interactive)) { - throw CommandException(LOCME("More than one install behavior argument provided"), ARG_QUERY); + throw CommandException(LOCME("More than one install behavior argument provided"), s_InstallCommand_ArgName_Query); } } } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.h b/src/AppInstallerCLICore/Commands/InstallCommand.h @@ -15,7 +15,7 @@ namespace AppInstaller::CLI std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const override; - void ValidateArguments(Invocation& inv) const override; + void ExecuteInternal(ExecutionContext& context) const override; + void ValidateArguments(ExecutionArgs& execArgs) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -29,10 +29,8 @@ namespace AppInstaller::CLI }; } - void RootCommand::ExecuteInternal(Invocation&, std::ostream& out, std::istream& in) const + void RootCommand::ExecuteInternal(ExecutionContext& context) const { - UNREFERENCED_PARAMETER(in); - - OutputHelp(out); + OutputHelp(context.Reporter); } } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.h b/src/AppInstallerCLICore/Commands/RootCommand.h @@ -14,6 +14,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const; }; } diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "Common.h" #include "SearchCommand.h" #include "Localization.h" #include "Workflows/SearchFlow.h" @@ -9,19 +8,30 @@ namespace AppInstaller::CLI { using namespace AppInstaller::Workflow; + using namespace std::string_view_literals; + + constexpr std::string_view s_SearchCommand_ArgName_Query = "query"sv; + constexpr std::string_view s_SearchCommand_ArgName_Id = "id"sv; + constexpr std::string_view s_SearchCommand_ArgName_Name = "name"sv; + constexpr std::string_view s_SearchCommand_ArgName_Moniker = "moniker"sv; + constexpr std::string_view s_SearchCommand_ArgName_Tag = "tag"sv; + constexpr std::string_view s_SearchCommand_ArgName_Command = "command"sv; + constexpr std::string_view s_SearchCommand_ArgName_Source = "source"sv; + constexpr std::string_view s_SearchCommand_ArgName_Count = "count"sv; + constexpr std::string_view s_SearchCommand_ArgName_Exact = "exact"sv; std::vector<Argument> SearchCommand::GetArguments() const { return { - Argument{ ARG_QUERY, LOCME("The query used to search for an app"), ArgumentType::Positional, false }, - Argument{ ARG_ID, LOCME("If specified, filter the results by id"), ArgumentType::Standard }, - Argument{ ARG_NAME, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, - Argument{ ARG_MONIKER, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, - Argument{ ARG_TAG, LOCME("If specified, filter the results by tag"), ArgumentType::Standard }, - Argument{ ARG_COMMAND, LOCME("If specified, filter the results by command"), ArgumentType::Standard }, - Argument{ ARG_SOURCE, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, - Argument{ ARG_COUNT, LOCME("If specified, find app and show only up to specified number of results."), ArgumentType::Standard }, - Argument{ ARG_EXACT, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ s_SearchCommand_ArgName_Query, ExecutionArgs::Type::Query, LOCME("The query used to search for an app"), ArgumentType::Positional, false }, + Argument{ s_SearchCommand_ArgName_Id, ExecutionArgs::Type::Id, LOCME("If specified, filter the results by id"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Name, ExecutionArgs::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Moniker, ExecutionArgs::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Tag, ExecutionArgs::Type::Tag, LOCME("If specified, filter the results by tag"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Command, ExecutionArgs::Type::Command, LOCME("If specified, filter the results by command"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Source, ExecutionArgs::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Count, ExecutionArgs::Type::Count, LOCME("If specified, find app and show only up to specified number of results."), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Exact, ExecutionArgs::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, }; } @@ -37,9 +47,9 @@ namespace AppInstaller::CLI }; } - void SearchCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + void SearchCommand::ExecuteInternal(ExecutionContext& context) const { - SearchFlow appSearch(inv, out, in); + SearchFlow appSearch{ context }; appSearch.Execute(); } diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.h b/src/AppInstallerCLICore/Commands/SearchCommand.h @@ -15,6 +15,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const override; + void ExecuteInternal(ExecutionContext& context) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "Common.h" #include "ShowCommand.h" #include "Localization.h" #include "Workflows\ShowFlow.h" @@ -9,19 +8,30 @@ namespace AppInstaller::CLI { using namespace AppInstaller::Workflow; + using namespace std::string_view_literals; + + constexpr std::string_view s_ShowCommand_ArgName_Query = "query"sv; + constexpr std::string_view s_ShowCommand_ArgName_Id = "id"sv; + constexpr std::string_view s_ShowCommand_ArgName_Name = "name"sv; + constexpr std::string_view s_ShowCommand_ArgName_Moniker = "moniker"sv; + constexpr std::string_view s_ShowCommand_ArgName_Version = "version"sv; + constexpr std::string_view s_ShowCommand_ArgName_Channel = "channel"sv; + constexpr std::string_view s_ShowCommand_ArgName_Source = "source"sv; + constexpr std::string_view s_ShowCommand_ArgName_Exact = "exact"sv; + constexpr std::string_view s_ShowCommand_ArgName_ListVersions = "listversions"sv; std::vector<Argument> ShowCommand::GetArguments() const { return { - Argument{ ARG_QUERY, LOCME("The query used to search for an app"), ArgumentType::Positional, true }, - Argument{ ARG_ID, LOCME("The id of the application to show info"), ArgumentType::Standard }, - Argument{ ARG_NAME, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, - Argument{ ARG_MONIKER, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, - Argument{ ARG_VERSION, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, - Argument{ ARG_CHANNEL, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, - Argument{ ARG_SOURCE, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, - Argument{ ARG_EXACT, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, - Argument{ ARG_LISTVERSIONS, LOCME("If specified, only show available versions of the app"), ArgumentType::Flag }, + Argument{ s_ShowCommand_ArgName_Query, ExecutionArgs::Type::Query, LOCME("The query used to search for an app"), ArgumentType::Positional, true }, + Argument{ s_ShowCommand_ArgName_Id, ExecutionArgs::Type::Id, LOCME("The id of the application to show info"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Name, ExecutionArgs::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Moniker, ExecutionArgs::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Version, ExecutionArgs::Type::Version, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Channel, ExecutionArgs::Type::Channel, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Source, ExecutionArgs::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Exact, ExecutionArgs::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ s_ShowCommand_ArgName_ListVersions, ExecutionArgs::Type::ListVersions, LOCME("If specified, only show available versions of the app"), ArgumentType::Flag }, }; } @@ -37,9 +47,9 @@ namespace AppInstaller::CLI }; } - void ShowCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + void ShowCommand::ExecuteInternal(ExecutionContext& context) const { - ShowFlow appShowInfo(inv, out, in); + ShowFlow appShowInfo{ context }; appShowInfo.Execute(); } diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.h b/src/AppInstallerCLICore/Commands/ShowCommand.h @@ -15,6 +15,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const override; + void ExecuteInternal(AppInstaller::CLI::ExecutionContext& context) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -3,16 +3,14 @@ #include "pch.h" #include "SourceCommand.h" #include "Localization.h" -#include "Workflows/WorkflowReporter.h" - namespace AppInstaller::CLI { using namespace std::string_view_literals; - constexpr std::string_view s_SourceCommand_ArgName_Name = "name"; - constexpr std::string_view s_SourceCommand_ArgName_Type = "type"; - constexpr std::string_view s_SourceCommand_ArgName_Arg = "arg"; + constexpr std::string_view s_SourceCommand_ArgName_Name = "name"sv; + constexpr std::string_view s_SourceCommand_ArgName_Type = "type"sv; + constexpr std::string_view s_SourceCommand_ArgName_Arg = "arg"sv; std::vector<std::unique_ptr<Command>> SourceCommand::GetCommands() const { @@ -36,17 +34,17 @@ namespace AppInstaller::CLI }; } - void SourceCommand::ExecuteInternal(Invocation&, std::ostream& out, std::istream&) const + void SourceCommand::ExecuteInternal(ExecutionContext& context) const { - OutputHelp(out); + OutputHelp(context.Reporter); } std::vector<Argument> SourceAddCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, LOCME("Name of the source for future reference"), ArgumentType::Positional, true }, - Argument{ s_SourceCommand_ArgName_Arg, LOCME("Argument given to the source"), ArgumentType::Positional, true }, - Argument{ s_SourceCommand_ArgName_Type, LOCME("Type of the source"), ArgumentType::Positional, false }, + Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source for future reference"), ArgumentType::Positional, true }, + Argument{ s_SourceCommand_ArgName_Arg, ExecutionArgs::Type::SourceArg, LOCME("Argument given to the source"), ArgumentType::Positional, true }, + Argument{ s_SourceCommand_ArgName_Type, ExecutionArgs::Type::SourceType, LOCME("Type of the source"), ArgumentType::Positional, false }, }; } @@ -62,34 +60,33 @@ namespace AppInstaller::CLI }; } - void SourceAddCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + void SourceAddCommand::ExecuteInternal(ExecutionContext& context) const { - std::string name = *inv.GetArg(s_SourceCommand_ArgName_Name); - std::string arg = *inv.GetArg(s_SourceCommand_ArgName_Arg); + std::string name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); + std::string arg = *context.Args.GetArg(ExecutionArgs::Type::SourceArg); std::string type; - if (inv.Contains(s_SourceCommand_ArgName_Type)) + if (context.Args.Contains(ExecutionArgs::Type::SourceType)) { - type = *inv.GetArg(s_SourceCommand_ArgName_Type); + type = *context.Args.GetArg(ExecutionArgs::Type::SourceType); } - out << LOCME("Adding source:") << std::endl; - out << " " << LOCME("Name: ") << name << std::endl; - out << " " << LOCME("Arg: ") << arg << std::endl; + context.Reporter.ShowMsg("Adding source:"); + context.Reporter.ShowMsg(" Name: " + name); + context.Reporter.ShowMsg(" Arg: " + arg); if (!type.empty()) { - out << " " << LOCME("Type: ") << type << std::endl; + context.Reporter.ShowMsg(" Type: " + type); } - Workflow::WorkflowReporter reporter(out, in); - reporter.ExecuteWithProgress(std::bind(Repository::AddSource, std::move(name), std::move(type), std::move(arg), std::placeholders::_1)); + context.Reporter.ExecuteWithProgress(std::bind(Repository::AddSource, std::move(name), std::move(type), std::move(arg), std::placeholders::_1)); - out << LOCME("Done") << std::endl; + context.Reporter.ShowMsg("Done"); } std::vector<Argument> SourceListCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, LOCME("Name of the source to list full details for"), ArgumentType::Positional, false }, + Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source to list full details for"), ArgumentType::Positional, false }, }; } @@ -105,48 +102,50 @@ namespace AppInstaller::CLI }; } - void SourceListCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream&) const + void SourceListCommand::ExecuteInternal(ExecutionContext& context) const { std::vector<Repository::SourceDetails> sources = Repository::GetSources(); - if (inv.Contains(s_SourceCommand_ArgName_Name)) + if (context.Args.Contains(ExecutionArgs::Type::SourceName)) { - const std::string& name = *inv.GetArg(s_SourceCommand_ArgName_Name); + const std::string& name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); auto itr = std::find_if(sources.begin(), sources.end(), [name](const Repository::SourceDetails& sd) { return Utility::CaseInsensitiveEquals(sd.Name, name); }); if (itr == sources.end()) { - out << LOCME("No source with the given name was found: ") << name << std::endl; + context.Reporter.ShowMsg("No source with the given name was found: " + name); } else { - out << LOCME("Name") << ": " << itr->Name << std::endl; - out << LOCME("Type") << ": " << itr->Type << std::endl; - out << LOCME("Arg") << ": " << itr->Arg << std::endl; - out << LOCME("Data") << ": " << itr->Data << std::endl; + context.Reporter.ShowMsg("Name: " + itr->Name); + context.Reporter.ShowMsg("Type: " + itr->Type); + context.Reporter.ShowMsg("Arg: " + itr->Arg); + context.Reporter.ShowMsg("Data: " + itr->Data); if (itr->LastUpdateTime == Utility::ConvertUnixEpochToSystemClock(0)) { - out << LOCME("Last Update") << ": <never>" << std::endl; + context.Reporter.ShowMsg("Last Update: <never>"); } else { - out << LOCME("Last Update") << ": " << itr->LastUpdateTime << std::endl; + std::stringstream stream; + stream << itr->LastUpdateTime; + context.Reporter.ShowMsg("Last Update: " + stream.str()); } } } else { - out << LOCME("Current sources:") << std::endl; + context.Reporter.ShowMsg("Current sources:"); if (sources.empty()) { - out << LOCME(" <none>") << std::endl; + context.Reporter.ShowMsg(" <none>"); } else { for (const auto& source : sources) { - out << " " << source.Name << " => " << source.Arg << std::endl; + context.Reporter.ShowMsg(" " + source.Name + " => " + source.Arg); } } } @@ -155,7 +154,7 @@ namespace AppInstaller::CLI std::vector<Argument> SourceUpdateCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, LOCME("Name of the source to update"), ArgumentType::Positional, false }, + Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source to update"), ArgumentType::Positional, false }, }; } @@ -171,33 +170,32 @@ namespace AppInstaller::CLI }; } - void SourceUpdateCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + void SourceUpdateCommand::ExecuteInternal(ExecutionContext& context) const { - Workflow::WorkflowReporter reporter(out, in); - - if (inv.Contains(s_SourceCommand_ArgName_Name)) + if (context.Args.Contains(ExecutionArgs::Type::SourceName)) { - const std::string& name = *inv.GetArg(s_SourceCommand_ArgName_Name); - out << LOCME("Updating source: ") << name << "..." << std::endl; - if (!reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, name, std::placeholders::_1))) + const std::string& name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); + context.Reporter.ShowMsg("Updating source: " + name + "..."); + if (!context.Reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, name, std::placeholders::_1))) { - out << std::endl << LOCME(" Could not find a source by that name.") << std::endl; + context.Reporter.EmptyLine(); + context.Reporter.ShowMsg(" Could not find a source by that name.", ExecutionReporter::Level::Warning); } else { - out << LOCME("Done") << std::endl; + context.Reporter.ShowMsg("Done"); } } else { - out << LOCME("Updating all sources...") << std::endl; + context.Reporter.ShowMsg("Updating all sources..."); std::vector<Repository::SourceDetails> sources = Repository::GetSources(); for (const auto& sd : sources) { - out << LOCME("Updating source: ") << sd.Name << "..." << std::endl; - reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, sd.Name, std::placeholders::_1)); - out << LOCME("Done.") << std::endl; + context.Reporter.ShowMsg("Updating source: " + sd.Name + "..."); + context.Reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, sd.Name, std::placeholders::_1)); + context.Reporter.ShowMsg(LOCME("Done.") ); } } } @@ -205,7 +203,7 @@ namespace AppInstaller::CLI std::vector<Argument> SourceRemoveCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, LOCME("Name of the source to update"), ArgumentType::Positional, true }, + Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source to remove"), ArgumentType::Positional, true }, }; } @@ -221,19 +219,17 @@ namespace AppInstaller::CLI }; } - void SourceRemoveCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + void SourceRemoveCommand::ExecuteInternal(ExecutionContext& context) const { - Workflow::WorkflowReporter reporter(out, in); - - const std::string& name = *inv.GetArg(s_SourceCommand_ArgName_Name); - out << LOCME("Removing source: ") << name << "..." << std::endl; - if (!reporter.ExecuteWithProgress(std::bind(Repository::RemoveSource, name, std::placeholders::_1))) + const std::string& name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); + context.Reporter.ShowMsg("Removing source: " + name + "..."); + if (!context.Reporter.ExecuteWithProgress(std::bind(Repository::RemoveSource, name, std::placeholders::_1))) { - out << LOCME("Could not find a source by that name.") << std::endl; + context.Reporter.ShowMsg("Could not find a source by that name.", ExecutionReporter::Level::Warning); } else { - out << LOCME("Done") << std::endl; + context.Reporter.ShowMsg("Done"); } } } diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.h b/src/AppInstallerCLICore/Commands/SourceCommand.h @@ -15,7 +15,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const; }; struct SourceAddCommand final : public Command @@ -28,7 +28,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const override; }; struct SourceListCommand final : public Command @@ -41,7 +41,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const override; }; struct SourceUpdateCommand final : public Command @@ -54,7 +54,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const override; }; struct SourceRemoveCommand final : public Command @@ -67,6 +67,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; + virtual void ExecuteInternal(ExecutionContext& context) const override; }; } diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Public/AppInstallerCLICore.h" #include "Commands/RootCommand.h" +#include "ExecutionContext.h" using namespace winrt; using namespace winrt::Windows::Foundation; @@ -23,6 +24,8 @@ namespace AppInstaller::CLI Logging::Telemetry().LogStartup(); + ExecutionContext context{ std::cout, std::cin }; + // Convert incoming wide char args to UTF8 std::vector<std::string> utf8Args; for (int i = 1; i < argc; ++i) @@ -56,34 +59,34 @@ namespace AppInstaller::CLI Logging::Telemetry().LogCommand(commandToExecute->Name()); - commandToExecute->ParseArguments(invocation); - commandToExecute->ValidateArguments(invocation); + commandToExecute->ParseArguments(invocation, context.Args); + commandToExecute->ValidateArguments(context.Args); } // Exceptions specific to parsing the arguments of a command catch (const CommandException& ce) { - commandToExecute->OutputHelp(std::cout, &ce); + commandToExecute->OutputHelp(context.Reporter, &ce); AICLI_LOG(CLI, Error, << "Error encountered parsing command line: " << ce.Message()); return APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS; } try { - commandToExecute->Execute(invocation, std::cout, std::cin); + commandToExecute->Execute(context); } // Exceptions that may occur in the process of executing an arbitrary command catch (const winrt::hresult_error& hre) { // TODO: Better error output std::string message = Utility::ConvertToUTF8(hre.message()); - std::cout << "An error occured while executing the command: " << message << std::endl; + context.Reporter.ShowMsg("An error occured while executing the command: " + message, ExecutionReporter::Level::Error); AICLI_LOG(CLI, Error, << "Error encountered executing command: " << message); return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } catch (const std::exception& e) { // TODO: Better error output - std::cout << "An error occured while executing the command: " << e.what() << std::endl; + context.Reporter.ShowMsg("An error occured while executing the command: " + std::string(e.what()), ExecutionReporter::Level::Error); AICLI_LOG(CLI, Error, << "Error encountered executing command: " << e.what()); return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <string> +#include <map> +#include <vector> + +namespace AppInstaller::CLI +{ + struct ExecutionArgs + { + enum class Type + { + // Args to specify where to get app + Query, // Query to be performed against index + Manifest, // Provide the app manifest directly + + // Query filtering criteria and query behavior + Id, + Name, + Moniker, + Tag, + Command, + Source, // Index source to be queried against + Count, // Maximun query results + Exact, // Exact match required + + // Manifest selection behavior after an app is found + Version, + Channel, + + // Install behavior + Interactive, + Silent, + Language, + Log, + Override, //Override args are (and the only args) directly passed to installer + InstallLocation, + + //Source Command + SourceName, + SourceType, + SourceArg, + + // Other + ListVersions, // Used in Show command to list all available versions of an app + Help, // Show command usage + }; + + bool Contains(Type arg) const { return (m_parsedArgs.count(arg) != 0); } + + const std::vector<std::string>* GetArgs(Type arg) const + { + auto itr = m_parsedArgs.find(arg); + return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); + } + + const std::string* GetArg(Type arg) const + { + auto itr = m_parsedArgs.find(arg); + + if (itr == m_parsedArgs.end()) + { + return nullptr; + } + + return &(itr->second[0]); + } + + size_t GetCount(Type arg) const + { + auto args = GetArgs(arg); + return (args ? args->size() : 0); + } + + bool AddArg(Type arg) + { + return m_parsedArgs[arg].empty(); + } + void AddArg(Type arg, std::string value) + { + m_parsedArgs[arg].emplace_back(std::move(value)); + } + + private: + std::map<Type, std::vector<std::string>> m_parsedArgs; + }; +} diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionReporter.h" +#include "ExecutionArgs.h" + +namespace AppInstaller::CLI +{ + struct ExecutionContext + { + ExecutionReporter Reporter; + ExecutionArgs Args; + + ExecutionContext(std::ostream& out, std::istream& in) : Reporter(out, in) {} + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "ExecutionReporter.h" + +namespace AppInstaller::CLI +{ + void IndefiniteSpinner::ShowSpinner() + { + if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + { + m_spinnerRunning = true; + m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); + } + } + + void IndefiniteSpinner::StopSpinner() + { + if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) + { + m_canceled = true; + m_spinnerJob.get(); + } + } + + void IndefiniteSpinner::ShowSpinnerInternal() + { + char spinnerChars[] = { '-', '\\', '|', '/' }; + + // First wait for a small amount of time to enable a fast task to skip + // showing anything, or a progress task to skip straight to progress. + Sleep(100); + + for (int i = 0; !m_canceled; i++) { + out << '\b' << spinnerChars[i] << std::flush; + + if (i == 3) + { + i = -1; + } + + Sleep(250); + } + + out << '\b'; + m_canceled = false; + m_spinnerRunning = false; + } + + void ProgressBar::ShowProgress(bool running, uint64_t progress) + { + if (running) + { + if (m_isVisible) + { + out << "\rProgress: " << progress; + } + else + { + out << "Progress: " << progress; + m_isVisible = true; + } + } + else + { + if (m_isVisible) + { + out << std::endl; + m_isVisible = false; + } + } + } + + bool ExecutionReporter::PromptForBoolResponse(const std::string& msg, Level level) + { + UNREFERENCED_PARAMETER(level); + + out << msg << " (Y|N)" << std::endl; + + char response; + in.get(response); + + return tolower(response) == 'y'; + } + + void ExecutionReporter::ShowMsg(const std::string& msg, Level level) + { + UNREFERENCED_PARAMETER(level); + + // Todo: color output using level and possibly other factors. + out << msg << std::endl; + } + + void ExecutionReporter::ShowProgress(bool running, uint64_t progress) + { + m_progressBar.ShowProgress(running, progress); + } + + void ExecutionReporter::ShowIndefiniteProgress(bool running) + { + if (running) + { + m_spinner.ShowSpinner(); + } + else + { + m_spinner.StopSpinner(); + } + } + + void ExecutionReporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + { + UNREFERENCED_PARAMETER(type); + ShowIndefiniteProgress(false); + ShowProgress(true, (maximum ? static_cast<uint64_t>((static_cast<double>(current) / maximum) * 100) : current)); + } +} diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerProgress.h" + +#include <wil/resource.h> + +#include <atomic> +#include <future> +#include <istream> +#include <ostream> +#include <string> + +namespace AppInstaller::CLI +{ + // Class to print a indefinite spinner. + class IndefiniteSpinner + { + public: + IndefiniteSpinner(std::ostream& stream) : out(stream) {}; + + void ShowSpinner(); + void StopSpinner(); + + private: + std::atomic<bool> m_canceled = false; + std::atomic<bool> m_spinnerRunning = false; + std::future<void> m_spinnerJob; + std::ostream& out; + + void ShowSpinnerInternal(); + }; + + // Todo: Need to implement real progress bar. Only prints progress number now. + class ProgressBar + { + public: + ProgressBar(std::ostream& stream) : out(stream) {}; + + void ShowProgress(bool running, uint64_t progress); + + private: + std::atomic<bool> m_isVisible = false; + std::ostream& out; + }; + + // WorkflowReporter should be the central place to show workflow status to user. + // Todo: need to implement actual console output to show color, progress bar, etc + struct ExecutionReporter : public IProgressCallback + { + enum class Level + { + Verbose, + Info, + Warning, + Error, + }; + + ExecutionReporter(std::ostream& outStream, std::istream& inStream) : + out(outStream), in(inStream), m_progressBar(outStream), m_spinner(outStream) {}; + + void EmptyLine() { out << std::endl; } + + bool PromptForBoolResponse(const std::string& msg, Level level = Level::Info); + + void ShowMsg(const std::string& msg, Level level = Level::Info); + + // Used to show definite progress. + // running: shows progress bar if set to true, dismisses progress bar if set to false + void ShowProgress(bool running, uint64_t progress); + + // Used to show indefinite progress. Currently an indefinite spinner is the form of + // showing indefinite progress. + // running: shows indefinite progress if set to true, stops indefinite progress if set to false + void ShowIndefiniteProgress(bool running); + + // IProgressCallback + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; + bool IsCancelled() override { return false; } + [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function<void()>&&) override { return {}; } + + // Runs the given callable of type: auto(IProgressCallback&) + template <typename F> + auto ExecuteWithProgress(F&& f) + { + ProgressCallback callback(this); + ShowIndefiniteProgress(true); + + auto hideProgress = wil::scope_exit([this]() + { + ShowIndefiniteProgress(false); + ShowProgress(false, 0); + }); + return f(callback); + } + + private: + std::ostream& out; + std::istream& in; + IndefiniteSpinner m_spinner; + ProgressBar m_progressBar; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Invocation.h b/src/AppInstallerCLICore/Invocation.h @@ -8,8 +8,6 @@ namespace AppInstaller::CLI { struct Invocation { - using ArgString = char const*; - Invocation(std::vector<std::string>&& args) : m_args(std::move(args)) {} struct iterator @@ -41,46 +39,8 @@ namespace AppInstaller::CLI iterator end() { return { m_args.size(), m_args }; } void consume(const iterator& i) { m_currentFirstArg = i.index() + 1; } - bool Contains(std::string_view name) const { return (m_parsedArgs.count(name) != 0); } - const std::vector<std::string>* GetArgs(std::string_view name) const - { - auto itr = m_parsedArgs.find(name); - return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); - } - - const std::string* GetArg(std::string_view name) const - { - auto itr = m_parsedArgs.find(name); - - if (itr == m_parsedArgs.end()) - { - return nullptr; - } - - return &(itr->second[0]); - } - - size_t GetCount(std::string_view name) const - { - auto args = GetArgs(name); - return (args ? args->size() : 0); - } - - bool AddArg(std::string_view name) - { - AICLI_LOG(CLI, Verbose, << "Found flag: " << name); - return m_parsedArgs[name].empty(); - } - void AddArg(std::string_view name, std::string value) - { - AICLI_LOG(CLI, Verbose, << "Found argument with value: " << name << " => " << value); - m_parsedArgs[name].emplace_back(std::move(value)); - } - private: std::vector<std::string> m_args; size_t m_currentFirstArg = 0; - - std::map<std::string_view, std::vector<std::string>> m_parsedArgs; }; } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -2,14 +2,12 @@ // Licensed under the MIT License. #include "pch.h" -#include "Commands/Common.h" #include "InstallFlow.h" #include "ManifestComparator.h" #include "ShellExecuteInstallerHandler.h" #include "MsixInstallerHandler.h" -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Management::Deployment; +using namespace AppInstaller::CLI; using namespace AppInstaller::Utility; using namespace AppInstaller::Manifest; @@ -17,9 +15,9 @@ namespace AppInstaller::Workflow { void InstallFlow::Execute() { - if (m_argsRef.Contains(CLI::ARG_MANIFEST)) + if (m_argsRef.Contains(ExecutionArgs::Type::Manifest)) { - m_manifest = Manifest::Manifest::CreateFromPath(*(m_argsRef.GetArg(CLI::ARG_MANIFEST))); + m_manifest = Manifest::Manifest::CreateFromPath(*(m_argsRef.GetArg(ExecutionArgs::Type::Manifest))); InstallInternal(); } else @@ -39,7 +37,7 @@ namespace AppInstaller::Workflow Logging::Telemetry().LogManifestFields(m_manifest.Name, m_manifest.Version); // Select Installer - ManifestComparator manifestComparator(m_manifest, m_reporter); + ManifestComparator manifestComparator(m_manifest, m_reporterRef); m_selectedInstaller = manifestComparator.GetPreferredInstaller(m_argsRef); auto installerHandler = GetInstallerHandler(); @@ -53,12 +51,12 @@ namespace AppInstaller::Workflow auto app = m_searchResult.Matches.at(0).Application.get(); AICLI_LOG(CLI, Info, << "Found one app. App id: " << app->GetId() << " App name: " << app->GetName()); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Found app: " + app->GetName()); + m_reporterRef.ShowMsg("Found app: " + app->GetName()); // Todo: handle failure if necessary after real search is in place m_manifest = app->GetManifest( - m_argsRef.Contains(CLI::ARG_VERSION) ? *m_argsRef.GetArg(CLI::ARG_VERSION) : "", - m_argsRef.Contains(CLI::ARG_CHANNEL) ? *m_argsRef.GetArg(CLI::ARG_CHANNEL) : "" + m_argsRef.Contains(ExecutionArgs::Type::Version) ? *m_argsRef.GetArg(ExecutionArgs::Type::Version) : "", + m_argsRef.Contains(ExecutionArgs::Type::Channel) ? *m_argsRef.GetArg(ExecutionArgs::Type::Channel) : "" ); } @@ -72,9 +70,9 @@ namespace AppInstaller::Workflow case ManifestInstaller::InstallerTypeEnum::Msi: case ManifestInstaller::InstallerTypeEnum::Nullsoft: case ManifestInstaller::InstallerTypeEnum::Wix: - return std::make_unique<ShellExecuteInstallerHandler>(m_selectedInstaller, m_argsRef, m_reporter); + return std::make_unique<ShellExecuteInstallerHandler>(m_selectedInstaller, m_contextRef); case ManifestInstaller::InstallerTypeEnum::Msix: - return std::make_unique<MsixInstallerHandler>(m_selectedInstaller, m_argsRef, m_reporter); + return std::make_unique<MsixInstallerHandler>(m_selectedInstaller, m_contextRef); default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -4,17 +4,15 @@ #pragma once #include "Common.h" #include "WorkflowBase.h" -#include "Invocation.h" #include "InstallerHandlerBase.h" -#include "WorkflowReporter.h" +#include "ExecutionContext.h" namespace AppInstaller::Workflow { class InstallFlow : public WorkflowBase { public: - InstallFlow(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - WorkflowBase(args, outStream, inStream) {} + InstallFlow(AppInstaller::CLI::ExecutionContext& context) : WorkflowBase(context) {} // Execute will perform a query against index and do app install if a target app is found. // If a manifest is given with /manifest, use the manifest and no index search is performed. diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp @@ -4,6 +4,7 @@ #include "Common.h" #include "InstallerHandlerBase.h" +using namespace AppInstaller::CLI; using namespace AppInstaller::Manifest; namespace AppInstaller::Workflow @@ -24,7 +25,7 @@ namespace AppInstaller::Workflow if (!hash) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Package download canceled."); + m_reporterRef.ShowMsg("Package download canceled."); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package download canceled"); } @@ -39,16 +40,16 @@ namespace AppInstaller::Workflow << " SHA256 from download: " << Utility::SHA256::ConvertToString(hash.value())); - if (!m_reporterRef.PromptForBoolResponse(WorkflowReporter::Level::Warning, "Package hash verification failed. Continue?")) + if (!m_reporterRef.PromptForBoolResponse("Package hash verification failed. Continue?", ExecutionReporter::Level::Warning)) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Canceled. Package hash mismatch."); + m_reporterRef.ShowMsg("Canceled. Package hash mismatch.", ExecutionReporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package installation canceled"); } } else { AICLI_LOG(CLI, Info, << "Downloaded installer hash verified"); - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully verified SHA256."); + m_reporterRef.ShowMsg("Successfully verified SHA256."); } m_downloadedInstaller = tempInstallerPath; diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h @@ -2,9 +2,9 @@ // Licensed under the MIT License. #pragma once -#include "pch.h" -#include "Invocation.h" -#include "WorkflowReporter.h" +#include <string> +#include "Manifest/Manifest.h" +#include "ExecutionContext.h" namespace AppInstaller::Workflow { @@ -31,13 +31,12 @@ namespace AppInstaller::Workflow protected: InstallerHandlerBase( const Manifest::ManifestInstaller& manifestInstaller, - const CLI::Invocation& args, - WorkflowReporter& reporter) : - m_manifestInstallerRef(manifestInstaller), m_reporterRef(reporter), m_argsRef(args) {}; + AppInstaller::CLI::ExecutionContext& context) : + m_manifestInstallerRef(manifestInstaller), m_reporterRef(context.Reporter), m_argsRef(context.Args) {}; const Manifest::ManifestInstaller& m_manifestInstallerRef; - const CLI::Invocation& m_argsRef; - WorkflowReporter& m_reporterRef; + const AppInstaller::CLI::ExecutionArgs& m_argsRef; + AppInstaller::CLI::ExecutionReporter& m_reporterRef; std::filesystem::path m_downloadedInstaller; }; } diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -3,9 +3,9 @@ #include "pch.h" #include "Common.h" -#include "WorkflowReporter.h" #include "ManifestComparator.h" +using namespace AppInstaller::CLI; using namespace AppInstaller::Manifest; namespace AppInstaller::Workflow @@ -38,7 +38,7 @@ namespace AppInstaller::Workflow return true; } - ManifestInstaller ManifestComparator::GetPreferredInstaller(const AppInstaller::CLI::Invocation&) + ManifestInstaller ManifestComparator::GetPreferredInstaller(const ExecutionArgs&) { AICLI_LOG(CLI, Info, << "Starting installer selection."); @@ -48,7 +48,7 @@ namespace AppInstaller::Workflow // If the first one is inapplicable, then no installer is applicable. if (Utility::IsApplicableArchitecture(m_manifestRef.Installers[0].Arch) == -1) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "No applicable installer found."); + m_reporterRef.ShowMsg("No applicable installer found.", ExecutionReporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_WORKFLOW_FAILED), "No installer with applicable architecture found."); } @@ -57,14 +57,14 @@ namespace AppInstaller::Workflow AICLI_LOG(CLI, Info, << "Completed installer selection."); AICLI_LOG(CLI, Verbose, << "Selected installer arch: " << (int)selectedInstaller.Arch); AICLI_LOG(CLI, Verbose, << "Selected installer url: " << selectedInstaller.Url); - AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << selectedInstaller.InstallerType); + AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << Manifest::ManifestInstaller::InstallerTypeToString(selectedInstaller.InstallerType)); AICLI_LOG(CLI, Verbose, << "Selected installer scope: " << selectedInstaller.Scope); AICLI_LOG(CLI, Verbose, << "Selected installer language: " << selectedInstaller.Language); return selectedInstaller; } - ManifestLocalization ManifestComparator::GetPreferredLocalization(const AppInstaller::CLI::Invocation&) + ManifestLocalization ManifestComparator::GetPreferredLocalization(const ExecutionArgs&) { AICLI_LOG(CLI, Info, << "Starting localization selection."); diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "Invocation.h" +#include "ExecutionContext.h" namespace AppInstaller::Workflow { @@ -26,14 +26,14 @@ namespace AppInstaller::Workflow class ManifestComparator { public: - ManifestComparator(AppInstaller::Manifest::Manifest& manifest, WorkflowReporter& reporter) : m_manifestRef(manifest), m_reporterRef(reporter) {} + ManifestComparator(AppInstaller::Manifest::Manifest& manifest, AppInstaller::CLI::ExecutionReporter& reporter) : m_manifestRef(manifest), m_reporterRef(reporter) {} - AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const AppInstaller::CLI::Invocation& args); - AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const AppInstaller::CLI::Invocation& args); + AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const AppInstaller::CLI::ExecutionArgs& args); + AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const AppInstaller::CLI::ExecutionArgs& args); private: AppInstaller::Manifest::Manifest& m_manifestRef; - WorkflowReporter& m_reporterRef; + AppInstaller::CLI::ExecutionReporter& m_reporterRef; }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp @@ -7,6 +7,7 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Management::Deployment; +using namespace AppInstaller::CLI; using namespace AppInstaller::Utility; using namespace AppInstaller::Manifest; @@ -40,16 +41,16 @@ namespace AppInstaller::Workflow << "Signature SHA256 from download: " << SHA256::ConvertToString(signatureHash)); - if (!m_reporterRef.PromptForBoolResponse(WorkflowReporter::Level::Warning, "Package hash verification failed. Continue?")) + if (!m_reporterRef.PromptForBoolResponse("Package hash verification failed. Continue?", ExecutionReporter::Level::Warning)) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Canceled. Package hash mismatch."); + m_reporterRef.ShowMsg("Canceled. Package hash mismatch.", ExecutionReporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package installation canceled"); } } else { AICLI_LOG(CLI, Info, << "Msix package signature hash verified"); - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully verified SHA256."); + m_reporterRef.ShowMsg("Successfully verified SHA256."); } m_useStreaming = true; @@ -65,9 +66,9 @@ namespace AppInstaller::Workflow Uri target = m_useStreaming ? Uri(Utility::ConvertToUTF16(m_manifestInstallerRef.Url)) : Uri(m_downloadedInstaller.c_str()); - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Starting package install..."); + m_reporterRef.ShowMsg("Starting package install..."); ExecuteInstallerAsync(target); - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed."); + m_reporterRef.ShowMsg("Successfully installed."); } void MsixInstallerHandler::ExecuteInstallerAsync(const winrt::Windows::Foundation::Uri& uri) diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h @@ -11,9 +11,8 @@ namespace AppInstaller::Workflow public: MsixInstallerHandler( const Manifest::ManifestInstaller& manifestInstaller, - const CLI::Invocation& args, - WorkflowReporter& reporter) : - InstallerHandlerBase(manifestInstaller, args, reporter) {} + AppInstaller::CLI::ExecutionContext& context) : + InstallerHandlerBase(manifestInstaller, context) {} // Download method just checks installer signature hash if signature hash // is provided in the manifest. Otherwise, Download will download the whole diff --git a/src/AppInstallerCLICore/Workflows/SearchFlow.cpp b/src/AppInstallerCLICore/Workflows/SearchFlow.cpp @@ -20,7 +20,7 @@ namespace AppInstaller::Workflow if (m_searchResult.Matches.size() == 0) { AICLI_LOG(CLI, Info, << "No app found matching input criteria"); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "No app found matching input criteria."); + m_reporterRef.ShowMsg("No app found matching input criteria."); } else { diff --git a/src/AppInstallerCLICore/Workflows/SearchFlow.h b/src/AppInstallerCLICore/Workflows/SearchFlow.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "Invocation.h" +#include "ExecutionContext.h" #include "WorkflowBase.h" namespace AppInstaller::Workflow @@ -10,8 +10,7 @@ namespace AppInstaller::Workflow class SearchFlow : public WorkflowBase { public: - SearchFlow(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - WorkflowBase(args, outStream, inStream) {} + SearchFlow(AppInstaller::CLI::ExecutionContext& context) : WorkflowBase(context) {} void Execute();; diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -2,9 +2,9 @@ // Licensed under the MIT License. #include "pch.h" #include "Common.h" -#include "Commands/Common.h" #include "ShellExecuteInstallerHandler.h" +using namespace AppInstaller::CLI; using namespace AppInstaller::Utility; using namespace AppInstaller::Manifest; @@ -17,7 +17,7 @@ namespace AppInstaller::Workflow THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Installer not downloaded yet"); } - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Installing package ..."); + m_reporterRef.ShowMsg("Installing package ..."); std::string installerArgs = GetInstallerArgs(); AICLI_LOG(CLI, Info, << "Installer args: " << installerArgs); @@ -28,23 +28,23 @@ namespace AppInstaller::Workflow std::bind(ExecuteInstaller, m_downloadedInstaller, installerArgs, - m_argsRef.Contains(CLI::ARG_INTERACTIVE), + m_argsRef.Contains(ExecutionArgs::Type::Interactive), std::placeholders::_1)); if (!installResult) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Installation abandoned"); + m_reporterRef.ShowMsg("Installation abandoned", ExecutionReporter::Level::Error); } else if (installResult.value() != 0) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Install failed. Exit code: " + std::to_string(installResult.value())); + m_reporterRef.ShowMsg("Install failed. Exit code: " + std::to_string(installResult.value()), ExecutionReporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Install failed. Installer task returned: %u", installResult.value()); } else { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed!"); + m_reporterRef.ShowMsg("Successfully installed!"); } } @@ -98,11 +98,11 @@ namespace AppInstaller::Workflow const std::map<ManifestInstaller::InstallerSwitchType, std::string>& installerSwitches = m_manifestInstallerRef.Switches; // Construct install experience arg. - if (m_argsRef.Contains(CLI::ARG_SILENT) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Silent) != installerSwitches.end()) + if (m_argsRef.Contains(ExecutionArgs::Type::Silent) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Silent) != installerSwitches.end()) { installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::Silent); } - else if (m_argsRef.Contains(CLI::ARG_INTERACTIVE) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Interactive) != installerSwitches.end()) + else if (m_argsRef.Contains(ExecutionArgs::Type::Interactive) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Interactive) != installerSwitches.end()) { installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::Interactive); } @@ -112,13 +112,13 @@ namespace AppInstaller::Workflow } // Construct language arg if necessary. - if (m_argsRef.Contains(CLI::ARG_LANGUAGE) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Language) != installerSwitches.end()) + if (m_argsRef.Contains(ExecutionArgs::Type::Language) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Language) != installerSwitches.end()) { installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Language); } // Construct install location arg if necessary. - if (m_argsRef.Contains(CLI::ARG_INSTALLLOCATION) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::InstallLocation) != installerSwitches.end()) + if (m_argsRef.Contains(ExecutionArgs::Type::InstallLocation) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::InstallLocation) != installerSwitches.end()) { installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::InstallLocation); } @@ -142,9 +142,9 @@ namespace AppInstaller::Workflow { // Populate <LogPath> with value from command line or temp path. std::string logPath; - if (m_argsRef.Contains(CLI::ARG_LOG)) + if (m_argsRef.Contains(ExecutionArgs::Type::Log)) { - logPath = *m_argsRef.GetArg(CLI::ARG_LOG); + logPath = *m_argsRef.GetArg(ExecutionArgs::Type::Log); } else { @@ -152,8 +152,11 @@ namespace AppInstaller::Workflow } Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_LOGPATH), logPath); - // Populate <InstallPath> with value from command line or current path. - Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), *m_argsRef.GetArg(CLI::ARG_INSTALLLOCATION)); + // Populate <InstallPath> with value from command line. + if (m_argsRef.Contains(ExecutionArgs::Type::InstallLocation)) + { + Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), *m_argsRef.GetArg(ExecutionArgs::Type::InstallLocation)); + } // Todo: language token support will be implemented later } @@ -161,9 +164,9 @@ namespace AppInstaller::Workflow std::string ShellExecuteInstallerHandler::GetInstallerArgs() { // If override switch is specified, use the override value as installer args. - if (m_argsRef.Contains(CLI::ARG_OVERRIDE)) + if (m_argsRef.Contains(ExecutionArgs::Type::Override)) { - return *m_argsRef.GetArg(CLI::ARG_OVERRIDE); + return *m_argsRef.GetArg(ExecutionArgs::Type::Override); } std::string installerArgs = GetInstallerArgsTemplate(); diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -15,9 +15,8 @@ namespace AppInstaller::Workflow public: ShellExecuteInstallerHandler( const Manifest::ManifestInstaller& manifestInstaller, - const CLI::Invocation& args, - WorkflowReporter& reporter) : - InstallerHandlerBase(manifestInstaller, args, reporter) {}; + AppInstaller::CLI::ExecutionContext& context) : + InstallerHandlerBase(manifestInstaller, context) {}; // Install is done though invoking SheelExecute on downloaded installer. void Install() override; diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -2,10 +2,10 @@ // Licensed under the MIT License. #include "pch.h" -#include "Commands/Common.h" #include "ShowFlow.h" #include "ManifestComparator.h" +using namespace AppInstaller::CLI; using namespace AppInstaller::Repository; namespace AppInstaller::Workflow @@ -16,7 +16,7 @@ namespace AppInstaller::Workflow if (WorkflowBase::EnsureOneMatchFromSearchResult()) { - if (m_argsRef.Contains(CLI::ARG_LISTVERSIONS)) + if (m_argsRef.Contains(ExecutionArgs::Type::ListVersions)) { ShowAppVersion(); } @@ -32,39 +32,40 @@ namespace AppInstaller::Workflow auto app = m_searchResult.Matches.at(0).Application.get(); auto manifest = app->GetManifest( - m_argsRef.Contains(CLI::ARG_VERSION) ? *m_argsRef.GetArg(CLI::ARG_VERSION) : "", - m_argsRef.Contains(CLI::ARG_CHANNEL) ? *m_argsRef.GetArg(CLI::ARG_CHANNEL) : "" + m_argsRef.Contains(ExecutionArgs::Type::Version) ? *m_argsRef.GetArg(ExecutionArgs::Type::Version) : "", + m_argsRef.Contains(ExecutionArgs::Type::Channel) ? *m_argsRef.GetArg(ExecutionArgs::Type::Channel) : "" ); - ManifestComparator manifestComparator(manifest, m_reporter); + ManifestComparator manifestComparator(manifest, m_reporterRef); auto selectedLocalization = manifestComparator.GetPreferredLocalization(m_argsRef); auto selectedInstaller = manifestComparator.GetPreferredInstaller(m_argsRef); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Id: " + manifest.Id); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Name: " + manifest.Name); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Version: " + manifest.Version); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Author: " + manifest.Author); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "AppMoniker: " + manifest.AppMoniker); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Description: " + selectedLocalization.Description); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Homepage: " + selectedLocalization.Homepage); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "License: " + selectedLocalization.LicenseUrl); + m_reporterRef.ShowMsg("Id: " + manifest.Id); + m_reporterRef.ShowMsg("Name: " + manifest.Name); + m_reporterRef.ShowMsg("Version: " + manifest.Version); + m_reporterRef.ShowMsg("Author: " + manifest.Author); + m_reporterRef.ShowMsg("AppMoniker: " + manifest.AppMoniker); + m_reporterRef.ShowMsg("Description: " + selectedLocalization.Description); + m_reporterRef.ShowMsg("Homepage: " + selectedLocalization.Homepage); + m_reporterRef.ShowMsg("License: " + selectedLocalization.LicenseUrl); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Installer info:" + manifest.Id); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "--Installer Language: " + selectedInstaller.Language); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "--Installer SHA256: " + Utility::SHA256::ConvertToString(selectedInstaller.Sha256)); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "--Installer Download Url: " + selectedInstaller.Url); + m_reporterRef.ShowMsg("Installer info:" + manifest.Id); + m_reporterRef.ShowMsg("--Installer Language: " + selectedInstaller.Language); + m_reporterRef.ShowMsg("--Installer SHA256: " + Utility::SHA256::ConvertToString(selectedInstaller.Sha256)); + m_reporterRef.ShowMsg("--Installer Download Url: " + selectedInstaller.Url); + m_reporterRef.ShowMsg("--Installer Type: " + Manifest::ManifestInstaller::InstallerTypeToString(selectedInstaller.InstallerType)); } void ShowFlow::ShowAppVersion() { auto app = m_searchResult.Matches.at(0).Application.get(); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Id: " + app->GetId()); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Name: " + app->GetName()); + m_reporterRef.ShowMsg("Id: " + app->GetId()); + m_reporterRef.ShowMsg("Name: " + app->GetName()); for (auto& version : app->GetVersions()) { - m_reporter.ShowMsg(WorkflowReporter::Level::Info, " Version: " + version.first + ", Channel: " + version.second); + m_reporterRef.ShowMsg(" Version: " + version.first + ", Channel: " + version.second); } } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.h b/src/AppInstallerCLICore/Workflows/ShowFlow.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "Invocation.h" +#include "ExecutionContext.h" #include "WorkflowBase.h" namespace AppInstaller::Workflow @@ -10,8 +10,7 @@ namespace AppInstaller::Workflow class ShowFlow : public WorkflowBase { public: - ShowFlow(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - WorkflowBase(args, outStream, inStream) {} + ShowFlow(AppInstaller::CLI::ExecutionContext& context) : WorkflowBase(context) {} void Execute();; diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -2,11 +2,11 @@ // Licensed under the MIT License. #include "pch.h" -#include "Commands/Common.h" #include "WorkflowBase.h" #include "Public/AppInstallerRepositorySearch.h" #include "Public/AppInstallerRepositorySource.h" +using namespace AppInstaller::CLI; using namespace AppInstaller::Repository; namespace AppInstaller::Workflow @@ -14,12 +14,12 @@ namespace AppInstaller::Workflow void WorkflowBase::OpenIndexSource() { std::string sourceName; - if (m_argsRef.Contains(CLI::ARG_SOURCE)) + if (m_argsRef.Contains(ExecutionArgs::Type::Source)) { - sourceName = *m_argsRef.GetArg(CLI::ARG_SOURCE); + sourceName = *m_argsRef.GetArg(ExecutionArgs::Type::Source); } - m_source = m_reporter.ExecuteWithProgress(std::bind(OpenSource, sourceName, std::placeholders::_1)); + m_source = m_reporterRef.ExecuteWithProgress(std::bind(OpenSource, sourceName, std::placeholders::_1)); } void WorkflowBase::IndexSearch() @@ -28,45 +28,45 @@ namespace AppInstaller::Workflow // Construct query MatchType matchType = MatchType::Fuzzy; - if (m_argsRef.Contains(CLI::ARG_EXACT)) + if (m_argsRef.Contains(ExecutionArgs::Type::Exact)) { matchType = MatchType::Exact; } SearchRequest searchRequest; - if (m_argsRef.Contains(CLI::ARG_QUERY)) + if (m_argsRef.Contains(ExecutionArgs::Type::Query)) { - searchRequest.Query.emplace(RequestMatch(matchType, *m_argsRef.GetArg(CLI::ARG_QUERY))); + searchRequest.Query.emplace(RequestMatch(matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Query))); } - if (m_argsRef.Contains(CLI::ARG_ID)) + if (m_argsRef.Contains(ExecutionArgs::Type::Id)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Id, matchType, *m_argsRef.GetArg(CLI::ARG_ID))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Id, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Id))); } - if (m_argsRef.Contains(CLI::ARG_NAME)) + if (m_argsRef.Contains(ExecutionArgs::Type::Name)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Name, matchType, *m_argsRef.GetArg(CLI::ARG_NAME))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Name, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Name))); } - if (m_argsRef.Contains(CLI::ARG_MONIKER)) + if (m_argsRef.Contains(ExecutionArgs::Type::Moniker)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Moniker, matchType, *m_argsRef.GetArg(CLI::ARG_MONIKER))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Moniker, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Moniker))); } - if (m_argsRef.Contains(CLI::ARG_TAG)) + if (m_argsRef.Contains(ExecutionArgs::Type::Tag)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Tag, matchType, *m_argsRef.GetArg(CLI::ARG_TAG))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Tag, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Tag))); } - if (m_argsRef.Contains(CLI::ARG_COMMAND)) + if (m_argsRef.Contains(ExecutionArgs::Type::Command)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Command, matchType, *m_argsRef.GetArg(CLI::ARG_COMMAND))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Command, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Command))); } - if (m_argsRef.Contains(CLI::ARG_COUNT)) + if (m_argsRef.Contains(ExecutionArgs::Type::Count)) { - searchRequest.MaximumResults = std::stoi(*m_argsRef.GetArg(CLI::ARG_COUNT)); + searchRequest.MaximumResults = std::stoi(*m_argsRef.GetArg(ExecutionArgs::Type::Count)); } m_searchResult = m_source->Search(searchRequest); @@ -77,14 +77,14 @@ namespace AppInstaller::Workflow if (m_searchResult.Matches.size() == 0) { AICLI_LOG(CLI, Info, << "No app found matching input criteria"); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "No app found matching input criteria."); + m_reporterRef.ShowMsg("No app found matching input criteria."); return false; } if (m_searchResult.Matches.size() > 1) { AICLI_LOG(CLI, Info, << "Multiple apps found matching input criteria"); - m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Multiple apps found matching input criteria. Please refine the input."); + m_reporterRef.ShowMsg("Multiple apps found matching input criteria. Please refine the input."); ReportSearchResult(); return false; } @@ -110,7 +110,7 @@ namespace AppInstaller::Workflow msg += ": " + match.MatchCriteria.Value + "]"; } - m_reporter.ShowMsg(WorkflowReporter::Level::Info, msg); + m_reporterRef.ShowMsg(msg); } } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -2,8 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "Invocation.h" -#include "WorkflowReporter.h" +#include "ExecutionContext.h" #include "Public/AppInstallerRepositorySearch.h" #include "Public/AppInstallerRepositorySource.h" @@ -12,11 +11,12 @@ namespace AppInstaller::Workflow class WorkflowBase { protected: - WorkflowBase(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - m_reporter(outStream, inStream), m_argsRef(args) {} + WorkflowBase(AppInstaller::CLI::ExecutionContext& context) : + m_contextRef(context), m_reporterRef(context.Reporter), m_argsRef(context.Args) {} - WorkflowReporter m_reporter; - const AppInstaller::CLI::Invocation& m_argsRef; + AppInstaller::CLI::ExecutionContext& m_contextRef; + AppInstaller::CLI::ExecutionReporter& m_reporterRef; + const AppInstaller::CLI::ExecutionArgs& m_argsRef; virtual void OpenIndexSource(); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp b/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp @@ -1,118 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "WorkflowReporter.h" - -namespace AppInstaller::Workflow -{ - void IndefiniteSpinner::ShowSpinner() - { - if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) - { - m_spinnerRunning = true; - m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); - } - } - - void IndefiniteSpinner::StopSpinner() - { - if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) - { - m_canceled = true; - m_spinnerJob.get(); - } - } - - void IndefiniteSpinner::ShowSpinnerInternal() - { - char spinnerChars[] = { '-', '\\', '|', '/' }; - - // First wait for a small amount of time to enable a fast task to skip - // showing anything, or a progress task to skip straight to progress. - Sleep(100); - - for (int i = 0; !m_canceled; i++) { - out << '\b' << spinnerChars[i] << std::flush; - - if (i == 3) - { - i = -1; - } - - Sleep(250); - } - - out << '\b'; - m_canceled = false; - m_spinnerRunning = false; - } - - void ProgressBar::ShowProgress(bool running, uint64_t progress) - { - if (running) - { - if (m_isVisible) - { - out << "\rProgress: " << progress; - } - else - { - out << "Progress: " << progress; - m_isVisible = true; - } - } - else - { - if (m_isVisible) - { - out << std::endl; - m_isVisible = false; - } - } - } - - bool WorkflowReporter::PromptForBoolResponse(Level level, const std::string& msg) - { - UNREFERENCED_PARAMETER(level); - - out << msg << " (Y|N)" << std::endl; - - char response; - in.get(response); - - return tolower(response) == 'y'; - } - - void WorkflowReporter::ShowMsg(Level level, const std::string& msg) - { - UNREFERENCED_PARAMETER(level); - - // Todo: color output using level and possibly other factors. - out << msg << std::endl; - } - - void WorkflowReporter::ShowProgress(bool running, uint64_t progress) - { - m_progressBar.ShowProgress(running, progress); - } - - void WorkflowReporter::ShowIndefiniteProgress(bool running) - { - if (running) - { - m_spinner.ShowSpinner(); - } - else - { - m_spinner.StopSpinner(); - } - } - - void WorkflowReporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) - { - UNREFERENCED_PARAMETER(type); - ShowIndefiniteProgress(false); - ShowProgress(true, (maximum ? static_cast<uint64_t>((static_cast<double>(current) / maximum) * 100) : current)); - } -} diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.h b/src/AppInstallerCLICore/Workflows/WorkflowReporter.h @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AppInstallerProgress.h" - -#include <wil/resource.h> - -#include <atomic> -#include <future> -#include <istream> -#include <ostream> -#include <string> - -namespace AppInstaller::Workflow -{ - // Class to print a indefinite spinner. - class IndefiniteSpinner - { - public: - IndefiniteSpinner(std::ostream& stream) : out(stream) {}; - - void ShowSpinner(); - void StopSpinner(); - - private: - std::atomic<bool> m_canceled = false; - std::atomic<bool> m_spinnerRunning = false; - std::future<void> m_spinnerJob; - std::ostream& out; - - void ShowSpinnerInternal(); - }; - - // Todo: Need to implement real progress bar. Only prints progress number now. - class ProgressBar - { - public: - ProgressBar(std::ostream& stream) : out(stream) {}; - - void ShowProgress(bool running, uint64_t progress); - - private: - std::atomic<bool> m_isVisible = false; - std::ostream& out; - }; - - // WorkflowReporter should be the central place to show workflow status to user. - // Todo: need to implement actual console output to show color, progress bar, etc - struct WorkflowReporter : public IProgressCallback - { - enum class Level - { - Verbose, - Info, - Warning, - Error, - }; - - WorkflowReporter(std::ostream& outStream, std::istream& inStream) : - out(outStream), in(inStream), m_progressBar(outStream), m_spinner(outStream) {}; - - bool PromptForBoolResponse(Level level, const std::string& msg); - - void ShowMsg(Level level, const std::string& msg); - - // Used to show definite progress. - // running: shows progress bar if set to true, dismisses progress bar if set to false - void ShowProgress(bool running, uint64_t progress); - - // Used to show indefinite progress. Currently an indefinite spinner is the form of - // showing indefinite progress. - // running: shows indefinite progress if set to true, stops indefinite progress if set to false - void ShowIndefiniteProgress(bool running); - - // IProgressCallback - void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - bool IsCancelled() override { return false; } - [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function<void()>&&) override { return {}; } - - // Runs the given callable of type: auto(IProgressCallback&) - template <typename F> - auto ExecuteWithProgress(F&& f) - { - ProgressCallback callback(this); - ShowIndefiniteProgress(true); - - auto hideProgress = wil::scope_exit([this]() - { - ShowIndefiniteProgress(false); - ShowProgress(false, 0); - }); - return f(callback); - } - - private: - std::ostream& out; - std::istream& in; - IndefiniteSpinner m_spinner; - ProgressBar m_progressBar; - }; -}- \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT License. #include "pch.h" #include "TestCommon.h" -#include "Commands/Common.h" #include "AppInstallerLogging.h" #include "Manifest/Manifest.h" #include "AppInstallerDownloader.h" @@ -17,6 +16,7 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Management::Deployment; using namespace TestCommon; +using namespace AppInstaller::CLI; using namespace AppInstaller::Manifest; using namespace AppInstaller::Repository; using namespace AppInstaller::Utility; @@ -27,8 +27,7 @@ class MsixInstallerHandlerTest : public MsixInstallerHandler public: MsixInstallerHandlerTest( const ManifestInstaller& manifestInstaller, - const AppInstaller::CLI::Invocation& args, - WorkflowReporter& reporter) : MsixInstallerHandler(manifestInstaller, args, reporter) {}; + ExecutionContext& context) : MsixInstallerHandler(manifestInstaller, context) {}; protected: @@ -49,8 +48,7 @@ class ShellExecuteInstallerHandlerTest : public ShellExecuteInstallerHandler public: ShellExecuteInstallerHandlerTest( const ManifestInstaller& manifestInstaller, - const AppInstaller::CLI::Invocation& args, - WorkflowReporter& reporter) : ShellExecuteInstallerHandler(manifestInstaller, args, reporter) {}; + ExecutionContext& context) : ShellExecuteInstallerHandler(manifestInstaller, context) {}; void Download() override { @@ -134,8 +132,7 @@ struct TestSource : public ISource class InstallFlowTest : public InstallFlow { public: - InstallFlowTest(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - InstallFlow(args, outStream, inStream) {} + InstallFlowTest(ExecutionContext& context) : InstallFlow(context) {} protected: std::unique_ptr<InstallerHandlerBase> GetInstallerHandler() override @@ -143,9 +140,9 @@ protected: switch (m_selectedInstaller.InstallerType) { case ManifestInstaller::InstallerTypeEnum::Exe: - return std::make_unique<ShellExecuteInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); + return std::make_unique<ShellExecuteInstallerHandlerTest>(m_selectedInstaller, m_contextRef); case ManifestInstaller::InstallerTypeEnum::Msix: - return std::make_unique<MsixInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); + return std::make_unique<MsixInstallerHandlerTest>(m_selectedInstaller, m_contextRef); default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } @@ -160,8 +157,7 @@ protected: class ShowFlowTest : public ShowFlow { public: - ShowFlowTest(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - ShowFlow(args, outStream, inStream) {} + ShowFlowTest(ExecutionContext& context) : ShowFlow(context) {} protected: @@ -176,9 +172,9 @@ TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow]") TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_Exe.yml").GetPath().u8string()); - InstallFlowTest testFlow(inv, installOutput, std::cin); + ExecutionContext context{ installOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yml").GetPath().u8string()); + InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -197,9 +193,9 @@ TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml").GetPath().u8string()); - InstallFlowTest testFlow(inv, installOutput, std::cin); + ExecutionContext context{ installOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml").GetPath().u8string()); + InstallFlowTest testFlow(context); REQUIRE_THROWS_WITH(testFlow.Execute(), Catch::Contains("No installer with applicable architecture found.")); INFO(installOutput.str()); @@ -212,10 +208,10 @@ TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow]") TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; + ExecutionContext context{ installOutput, std::cin }; // Todo: point to files from our repo when the repo goes public - inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml").GetPath().u8string()); - InstallFlowTest testFlow(inv, installOutput, std::cin); + context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml").GetPath().u8string()); + InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -233,10 +229,10 @@ TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow]") TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; + ExecutionContext context{ installOutput, std::cin }; // Todo: point to files from our repo when the repo goes public - inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml").GetPath().u8string()); - InstallFlowTest testFlow(inv, installOutput, std::cin); + context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml").GetPath().u8string()); + InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -251,27 +247,26 @@ TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow]") TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { - std::ostringstream installOutput; - WorkflowReporter reporter(installOutput, std::cin); - { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Default Msi type with no args passed in, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/passive") != std::string::npos); REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); } { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + context.Args.AddArg(ExecutionArgs::Type::Silent); + context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); + context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/quiet") != std::string::npos); REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); @@ -279,13 +274,14 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") } { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + context.Args.AddArg(ExecutionArgs::Type::Silent); + context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); + context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest @@ -294,23 +290,25 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") } { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Default Inno type with no args passed in, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/SILENT") != std::string::npos); REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); } { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + context.Args.AddArg(ExecutionArgs::Type::Silent); + context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); + context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); @@ -318,13 +316,14 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") } { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + context.Args.AddArg(ExecutionArgs::Type::Silent); + context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); + context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest @@ -333,14 +332,15 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") } { + std::ostringstream installOutput; + ExecutionContext context{ installOutput, std::cin }; // Override switch specified. The whole arg passed to installer is overrided. auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - inv.AddArg(AppInstaller::CLI::ARG_OVERRIDE, "/OverrideEverything"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + context.Args.AddArg(ExecutionArgs::Type::Silent); + context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); + context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + context.Args.AddArg(ExecutionArgs::Type::Override, "/OverrideEverything"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch } @@ -351,9 +351,9 @@ TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow]") TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnOne"); - InstallFlowTest testFlow(inv, installOutput, std::cin); + ExecutionContext context{ installOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnOne"); + InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -370,9 +370,9 @@ TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow]") TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow]") { std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnZero"); - InstallFlowTest testFlow(inv, installOutput, std::cin); + ExecutionContext context{ installOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnZero"); + InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -383,9 +383,9 @@ TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow]") TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow]") { std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnTwo"); - InstallFlowTest testFlow(inv, installOutput, std::cin); + ExecutionContext context{ installOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnTwo"); + InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -396,9 +396,9 @@ TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow]") TEST_CASE("InstallFlow_SearchAndShowAppInfo", "[ShowFlow]") { std::ostringstream showOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnOne"); - ShowFlowTest testFlow(inv, showOutput, std::cin); + ExecutionContext context{ showOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnOne"); + ShowFlowTest testFlow(context); testFlow.Execute(); INFO(showOutput.str()); @@ -412,10 +412,10 @@ TEST_CASE("InstallFlow_SearchAndShowAppInfo", "[ShowFlow]") TEST_CASE("InstallFlow_SearchAndShowAppVersion", "[ShowFlow]") { std::ostringstream showOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnOne"); - inv.AddArg(AppInstaller::CLI::ARG_LISTVERSIONS); - ShowFlowTest testFlow(inv, showOutput, std::cin); + ExecutionContext context{ showOutput, std::cin }; + context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnOne"); + context.Args.AddArg(ExecutionArgs::Type::ListVersions); + ShowFlowTest testFlow(context); testFlow.Execute(); INFO(showOutput.str()); diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp @@ -150,38 +150,38 @@ namespace AppInstaller::Manifest return result; } - std::ostream& operator<<(std::ostream& out, const ManifestInstaller::InstallerTypeEnum& installerType) + std::string ManifestInstaller::InstallerTypeToString(ManifestInstaller::InstallerTypeEnum installerType) { + std::string result = "Unknown"; + switch (installerType) { case ManifestInstaller::InstallerTypeEnum::Exe: - out << "Exe"; + result = "Exe"; break; case ManifestInstaller::InstallerTypeEnum::Inno: - out << "Inno"; + result = "Inno"; break; case ManifestInstaller::InstallerTypeEnum::Msi: - out << "Msi"; + result = "Msi"; break; case ManifestInstaller::InstallerTypeEnum::Msix: - out << "Msix"; + result = "Msix"; break; case ManifestInstaller::InstallerTypeEnum::Nullsoft: - out << "Nullsoft"; + result = "Nullsoft"; break; case ManifestInstaller::InstallerTypeEnum::Wix: - out << "Wix"; + result = "Wix"; break; case ManifestInstaller::InstallerTypeEnum::Zip: - out << "Zip"; + result = "Zip"; break; case ManifestInstaller::InstallerTypeEnum::Burn: - out << "Burn"; + result = "Burn"; break; - default: - out << "Unknown"; } - return out; + return result; } } diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.h b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.h @@ -93,7 +93,7 @@ namespace AppInstaller::Manifest // Populates ManifestInstaller // defaultInstaller: if an optional field is not found in the YAML node, the field will be populated with value from defaultInstaller. void PopulateInstallerFields(const YAML::Node& installerNode, const ManifestInstaller& defaultInstaller); - }; - std::ostream& operator<<(std::ostream& out, const ManifestInstaller::InstallerTypeEnum& installerType); + static std::string InstallerTypeToString(InstallerTypeEnum installerType); + }; } \ No newline at end of file