Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command aliases #2390

Merged
merged 15 commits into from
Aug 2, 2022
31 changes: 29 additions & 2 deletions src/AppInstallerCLICore/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ namespace AppInstaller::CLI

Command::Command(
std::string_view name,
std::vector<std::string_view> aliases,
Trenly marked this conversation as resolved.
Show resolved Hide resolved
std::string_view parent,
Command::Visibility visibility,
Settings::ExperimentalFeature::Feature feature,
Settings::TogglePolicy::Policy groupPolicy) :
m_name(name), m_visibility(visibility), m_feature(feature), m_groupPolicy(groupPolicy)
m_name(name), m_aliases(std::move(aliases)), m_visibility(visibility), m_feature(feature), m_groupPolicy(groupPolicy)
{
if (!parent.empty())
{
Expand Down Expand Up @@ -115,6 +116,7 @@ namespace AppInstaller::CLI
// Output the command preamble and command chain
infoOut << Resource::String::Usage << ": winget"_liv << Utility::LocIndView{ commandChain };

auto commandAliases = Aliases();
auto commands = GetVisibleCommands();
auto arguments = GetVisibleArguments();

Expand Down Expand Up @@ -183,6 +185,17 @@ namespace AppInstaller::CLI
std::endl <<
std::endl;

if (!commandAliases.empty())
{
infoOut << Resource::String::AvailableCommandAliases << std::endl;

for (const auto& commandAlias : commandAliases)
{
infoOut << " "_liv << Execution::HelpCommandEmphasis << commandAlias << std::endl;
}
infoOut << std::endl;
}

if (!commands.empty())
{
if (Name() == FullName())
Expand Down Expand Up @@ -291,7 +304,10 @@ namespace AppInstaller::CLI

for (auto& command : commands)
{
if (Utility::CaseInsensitiveEquals(*itr, command->Name()))
if (
Utility::CaseInsensitiveEquals(*itr, command->Name()) ||
Utility::CaseInsensitiveContains(command->Aliases(), *itr)
Trenly marked this conversation as resolved.
Show resolved Hide resolved
)
{
if (!ExperimentalFeature::IsEnabled(command->Feature()))
{
Expand Down Expand Up @@ -709,6 +725,17 @@ namespace AppInstaller::CLI
{
context.Reporter.Completion() << command->Name() << std::endl;
}
// Allow for command aliases to be auto-completed
if (!(command->Aliases()).empty() && !word.empty())
{
for (const auto& commandAlias : command->Aliases())
{
if (Utility::CaseInsensitiveStartsWith(commandAlias, word))
Trenly marked this conversation as resolved.
Show resolved Hide resolved
{
context.Reporter.Completion() << commandAlias << std::endl;
}
}
}
}
}

Expand Down
24 changes: 14 additions & 10 deletions src/AppInstallerCLICore/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,18 @@ namespace AppInstaller::CLI
};

Command(std::string_view name, std::string_view parent) :
Command(name, parent, Settings::ExperimentalFeature::Feature::None) {}
Command(std::string_view name, std::string_view parent, Command::Visibility visibility) :
Command(name, parent, visibility, Settings::ExperimentalFeature::Feature::None) {}
Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature) :
Command(name, parent, Command::Visibility::Show, feature) {}
Command(std::string_view name, std::string_view parent, Settings::TogglePolicy::Policy groupPolicy) :
Command(name, parent, Command::Visibility::Show, Settings::ExperimentalFeature::Feature::None, groupPolicy) {}
Command(std::string_view name, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature) :
Command(name, parent, visibility, feature, Settings::TogglePolicy::Policy::None) {}
Command(std::string_view name, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature, Settings::TogglePolicy::Policy groupPolicy);
Command(name, {}, parent) {}
Command(std::string_view name,std::vector<std::string_view> aliases, std::string_view parent) :
Command(name, aliases, parent, Settings::ExperimentalFeature::Feature::None) {}
Command(std::string_view name,std::vector<std::string_view> aliases, std::string_view parent, Command::Visibility visibility) :
Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None) {}
Command(std::string_view name,std::vector<std::string_view> aliases, std::string_view parent, Settings::ExperimentalFeature::Feature feature) :
Command(name, aliases, parent, Command::Visibility::Show, feature) {}
Command(std::string_view name,std::vector<std::string_view> aliases, std::string_view parent, Settings::TogglePolicy::Policy groupPolicy) :
Command(name, aliases, parent, Command::Visibility::Show, Settings::ExperimentalFeature::Feature::None, groupPolicy) {}
Command(std::string_view name,std::vector<std::string_view> aliases, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature) :
Command(name, aliases, parent, visibility, feature, Settings::TogglePolicy::Policy::None) {}
Command(std::string_view name,std::vector<std::string_view> aliases, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature, Settings::TogglePolicy::Policy groupPolicy);
Trenly marked this conversation as resolved.
Show resolved Hide resolved
virtual ~Command() = default;

Command(const Command&) = default;
Expand All @@ -78,6 +80,7 @@ namespace AppInstaller::CLI
constexpr static char ParentSplitChar = ':';

std::string_view Name() const { return m_name; }
const std::vector<std::string_view>& Aliases() const& { return m_aliases; }
const std::string& FullName() const { return m_fullName; }
Command::Visibility GetVisibility() const;
Settings::ExperimentalFeature::Feature Feature() const { return m_feature; }
Expand Down Expand Up @@ -111,6 +114,7 @@ namespace AppInstaller::CLI
private:
std::string_view m_name;
std::string m_fullName;
std::vector<std::string_view> m_aliases;
Command::Visibility m_visibility;
Settings::ExperimentalFeature::Feature m_feature;
Settings::TogglePolicy::Policy m_groupPolicy;
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/CompleteCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace AppInstaller::CLI
// be context sensitive in their data output.
struct CompleteCommand final : public Command
{
CompleteCommand(std::string_view parent) : Command("complete", parent, Visibility::Hidden) {}
CompleteCommand(std::string_view parent) : Command("complete", {}, parent, Visibility::Hidden) {}

std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/ExperimentalCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace AppInstaller::CLI
{
// This command is used as an example on how experimental features can be used.
// To enable this command set ExperimentalCmd = true in the settings file.
ExperimentalCommand(std::string_view parent) : Command("experimental", parent, Settings::ExperimentalFeature::Feature::ExperimentalCmd) {}
ExperimentalCommand(std::string_view parent) : Command("experimental", {}, parent, Settings::ExperimentalFeature::Feature::ExperimentalCmd) {}

virtual std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/InstallCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AppInstaller::CLI
{
struct InstallCommand final : public Command
{
InstallCommand(std::string_view parent) : Command("install", parent) {}
InstallCommand(std::string_view parent) : Command("install", { "add" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/ListCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace AppInstaller::CLI
// Command to get the set of installed packages on the system.
struct ListCommand final : public Command
{
ListCommand(std::string_view parent) : Command("list", parent) {}
ListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
3 changes: 2 additions & 1 deletion src/AppInstallerCLICore/Commands/SearchCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

namespace AppInstaller::CLI
{

struct SearchCommand final : public Command
{
SearchCommand(std::string_view parent) : Command("search", parent) {}
SearchCommand(std::string_view parent) : Command("search", { "find" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/SettingsCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AppInstaller::CLI
{
struct SettingsCommand final : public Command
{
SettingsCommand(std::string_view parent) : Command("settings", parent, Settings::TogglePolicy::Policy::Settings) {}
SettingsCommand(std::string_view parent) : Command("settings", { "config" }, parent, Settings::TogglePolicy::Policy::Settings) {}

std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/ShowCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AppInstaller::CLI
{
struct ShowCommand final : public Command
{
ShowCommand(std::string_view parent) : Command("show", parent) {}
ShowCommand(std::string_view parent) : Command("show", { "view" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
6 changes: 3 additions & 3 deletions src/AppInstallerCLICore/Commands/SourceCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace AppInstaller::CLI

struct SourceAddCommand final : public Command
{
SourceAddCommand(std::string_view parent) : Command("add", parent, Settings::TogglePolicy::Policy::AllowedSources) {}
SourceAddCommand(std::string_view parent) : Command("add", {}, parent, Settings::TogglePolicy::Policy::AllowedSources) {}

std::vector<Argument> GetArguments() const override;

Expand Down Expand Up @@ -54,7 +54,7 @@ namespace AppInstaller::CLI

struct SourceUpdateCommand final : public Command
{
SourceUpdateCommand(std::string_view parent) : Command("update", parent) {}
SourceUpdateCommand(std::string_view parent) : Command("update", { "refresh" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand All @@ -72,7 +72,7 @@ namespace AppInstaller::CLI
struct SourceRemoveCommand final : public Command
{
// We can remove user or default sources, so this is not gated by any single policy.
SourceRemoveCommand(std::string_view parent) : Command("remove", parent) {}
SourceRemoveCommand(std::string_view parent) : Command("remove", { "rm" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/UninstallCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AppInstaller::CLI
{
struct UninstallCommand final : public Command
{
UninstallCommand(std::string_view parent) : Command("uninstall", parent) {}
UninstallCommand(std::string_view parent) : Command("uninstall", { "remove", "rm" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/UpgradeCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AppInstaller::CLI
{
struct UpgradeCommand final : public Command
{
UpgradeCommand(std::string_view parent) : Command("upgrade", parent) {}
UpgradeCommand(std::string_view parent) : Command("upgrade", { "update" }, parent) {}

std::vector<Argument> GetArguments() const override;

Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled);
WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableHeader);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions);
Expand Down
8 changes: 8 additions & 0 deletions src/AppInstallerCLIE2ETests/SearchCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public void SearchQuery()
Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller"));
}

public void SearchUsingAlias()
{
var result = TestCommon.RunAICLICommand("find", "TestExampleInstaller");
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
Assert.True(result.StdOut.Contains("TestExampleInstaller"));
Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller"));
}

[Test]
public void SearchWithName()
{
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@
<data name="AvailableArguments" xml:space="preserve">
<value>The following arguments are available:</value>
</data>
<data name="AvailableCommandAliases" xml:space="preserve">
<value>The following command aliases are available:</value>
</data>
<data name="AvailableCommands" xml:space="preserve">
<value>The following commands are available:</value>
<comment>Commands the tool supports</comment>
Expand Down
50 changes: 46 additions & 4 deletions src/AppInstallerCLITests/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ using namespace AppInstaller::CLI::Execution;

std::string GetCommandName(const std::unique_ptr<Command>& command)
{
return std::string{ command->Name() };
return std::string{ command->FullName() };
}

std::vector<std::string_view> GetCommandAliases(const std::unique_ptr<Command>& command)
{
return std::vector<std::string_view> { command->Aliases() };
}

std::string GetArgumentName(const Argument& arg)
Expand Down Expand Up @@ -47,6 +52,11 @@ std::string GetArgumentAlias(const Argument& arg)
}
}

bool StringIsLowercase(const std::string& s)
{
return Utility::ToLower(s) == s;
}

template <typename Enumerable, typename Op>
void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set<std::string>& values, bool requireLower = true)
{
Expand All @@ -63,8 +73,7 @@ void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enu

if (requireLower)
{
std::string lowerVal = Utility::ToLower(valString);
REQUIRE(valString == lowerVal);
REQUIRE(StringIsLowercase(valString));
}

REQUIRE(values.find(valString) == values.end());
Expand All @@ -80,9 +89,42 @@ void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enu
EnsureStringsAreLowercaseAndNoCollisions(info, e, op, values, requireLower);
}

template <typename Enumerable, typename Op>
void EnsureVectorStringViewsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set<std::string>& values, bool requireLower = true)
{
INFO(info);

for (const auto& val : e)
{
std::vector<std::string_view> aliasVector = op(val);
std::vector<std::string> valVector(aliasVector.begin(), aliasVector.end());
if (valVector.empty())
{
continue;
}
// When op returns a vector, we need to ensure every value in the vector does not cause a collision
for (auto& valString : valVector)
{
INFO(valString);

if (requireLower)
{
REQUIRE(StringIsLowercase(valString));
}

REQUIRE(values.find(valString) == values.end());
values.emplace(std::move(valString));
}
}
}

void EnsureCommandConsistency(const Command& command)
{
EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " commands", command.GetCommands(), GetCommandName);
// Command names and aliases exist in the same space, so both need to be checked as a set
// However, collisions do not occur between levels, so the full name must be used to check for collision
Trenly marked this conversation as resolved.
Show resolved Hide resolved
std::unordered_set<std::string> allCommandAliasNames;
EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " commands", command.GetCommands(), GetCommandName, allCommandAliasNames);
EnsureVectorStringViewsAreLowercaseAndNoCollisions(command.FullName() + " aliases", command.GetCommands(), GetCommandAliases, allCommandAliasNames);

auto args = command.GetArguments();
Argument::GetCommon(args);
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCommonCore/AppInstallerStrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ namespace AppInstaller::Utility
return ToLower(a) == ToLower(b);
}

bool CaseInsensitiveContains(const std::vector<std::string_view>& a, std::string_view b)
{
auto B = ToLower(b);
return std::any_of(a.begin(), a.end(), [&](const std::string_view& s) { return ToLower(s) == B; });
}

bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b)
{
return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b);
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCommonCore/Public/AppInstallerStrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ namespace AppInstaller::Utility
// Use this if one of the values is a known value, and thus ToLower is sufficient.
bool CaseInsensitiveEquals(std::string_view a, std::string_view b);

// Returns if a UTF8 string is contained within a vector in a case insensitive manner.
bool CaseInsensitiveContains(const std::vector<std::string_view>& a, std::string_view b);

// Determines if string a starts with string b.
// Use this if one of the values is a known value, and thus ToLower is sufficient.
bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b);
Expand Down