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 support for bool, strings and arrays in Configuration settings #3135

Merged
merged 8 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 96 additions & 11 deletions src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,93 @@ namespace AppInstaller::CLI::Workflow
return {};
}

void OutputPropertyValue(OutputStream& out, const IPropertyValue property)
{
switch (property.Type())
{
case PropertyType::String:
out << ' ' << Utility::ConvertToUTF8(property.GetString()) << '\n';
break;
case PropertyType::Boolean:
out << ' ' << (property.GetBoolean() ? Utility::LocIndView("true") : Utility::LocIndView("false")) << '\n';
break;
case PropertyType::Int64:
out << ' ' << property.GetInt64() << '\n';
break;
default:
out << " [Debug:PropertyType="_liv << property.Type() << "]\n"_liv;
break;
}
}

void OutputValueSet(OutputStream& out, const ValueSet& valueSet, size_t indent);

void OutputValueSetAsArray(OutputStream& out, const ValueSet& valueSetArray, size_t indent)
{
Utility::LocIndString indentString{ std::string(indent, ' ') };

std::vector<std::pair<int, winrt::Windows::Foundation::IInspectable>> arrayValues;
for (const auto& arrayValue : valueSetArray)
{
if (arrayValue.Key() != L"treatAsArray")
{
arrayValues.emplace_back(std::make_pair(std::stoi(arrayValue.Key().c_str()), arrayValue.Value()));
}
}

std::sort(
arrayValues.begin(),
arrayValues.end(),
[](const std::pair<int, winrt::Windows::Foundation::IInspectable>& a, const std::pair<int, winrt::Windows::Foundation::IInspectable>& b)
{
return a.first < b.first;
});

for (const auto& arrayValue : arrayValues)
{
auto arrayObject = arrayValue.second;
IPropertyValue arrayProperty = arrayObject.try_as<IPropertyValue>();

out << indentString << "-";
if (arrayProperty)
{
OutputPropertyValue(out, arrayProperty);
}
else
{
ValueSet arraySubset = arrayObject.as<ValueSet>();
auto size = arraySubset.Size();
if (size > 0)
{
// First one is special.
auto first = arraySubset.First().Current();
out << ' ' << Utility::ConvertToUTF8(first.Key()) << ':';

auto object = first.Value();
IPropertyValue property = object.try_as<IPropertyValue>();
if (property)
{
OutputPropertyValue(out, property);
}
else
{
// If not an IPropertyValue, it must be a ValueSet
ValueSet subset = object.as<ValueSet>();
out << '\n';
OutputValueSet(out, subset, indent + 4);
}

if (size > 1)
{
arraySubset.Remove(first.Key());
OutputValueSet(out, arraySubset, indent + 2);
arraySubset.Insert(first.Key(), first.Value());
}
}
}
}
}

void OutputValueSet(OutputStream& out, const ValueSet& valueSet, size_t indent)
{
Utility::LocIndString indentString{ std::string(indent, ' ') };
Expand All @@ -109,23 +196,21 @@ namespace AppInstaller::CLI::Workflow
IPropertyValue property = object.try_as<IPropertyValue>();
if (property)
{
switch (property.Type())
{
case PropertyType::String:
out << ' ' << Utility::ConvertToUTF8(property.GetString()) << '\n';
break;
default:
// TODO: Sort out how we actually want to handle this given that we don't expect anything but strings
out << " [Debug:PropertyType="_liv << property.Type() << "]\n"_liv;
break;
}
OutputPropertyValue(out, property);
}
else
{
// If not an IPropertyValue, it must be a ValueSet
ValueSet subset = object.as<ValueSet>();
out << '\n';
OutputValueSet(out, subset, indent + 2);
if (subset.HasKey(L"treatAsArray"))
msftrubengu marked this conversation as resolved.
Show resolved Hide resolved
{
OutputValueSetAsArray(out, subset, indent + 2);
}
else
{
OutputValueSet(out, subset, indent + 2);
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,9 @@
<CopyFileToFolders Include="TestData\Manifest-Bad-MsixInstaller-PackageVersion.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Node-Types.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInstallerCLICore\AppInstallerCLICore.vcxproj">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,5 +834,8 @@
<CopyFileToFolders Include="TestData\Manifest-Bad-InconsistentSignedMsixBundleInstallerFields.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Node-Types.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions src/AppInstallerCLITests/TestData/Node-Types.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
IntegerUnquoted: 12345
IntegerSingleQuoted: '12345'
IntegerDoubleQuoted: "12345"

BooleanTrue: true
StringTrue: 'true'

BooleanFalse: false
StringFalse: 'false'

LocalTag: !myTag value
29 changes: 29 additions & 0 deletions src/AppInstallerCLITests/YamlManifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1162,3 +1162,32 @@ TEST_CASE("ManifestArpVersionRange", "[ManifestValidation]")
REQUIRE(arpRangeMultiArp.GetMinVersion().ToString() == "12.0");
REQUIRE(arpRangeMultiArp.GetMaxVersion().ToString() == "13.0");
}

TEST_CASE("YamlParserTypes", "[YAML]")
{
auto document = AppInstaller::YAML::Load(TestDataFile("Node-Types.yaml"));

auto intUnquoted = document["IntegerUnquoted"];
CHECK(intUnquoted.GetTagType() == Node::TagType::Int);

auto intSingleQuoted = document["IntegerSingleQuoted"];
CHECK(intSingleQuoted.GetTagType() == Node::TagType::Str);

auto intDoubleQuoted = document["IntegerDoubleQuoted"];
CHECK(intDoubleQuoted.GetTagType() == Node::TagType::Str);

auto boolTrue = document["BooleanTrue"];
CHECK(boolTrue.GetTagType() == Node::TagType::Bool);

auto strTrue = document["StringTrue"];
CHECK(strTrue.GetTagType() == Node::TagType::Str);

auto boolFalse = document["BooleanFalse"];
CHECK(boolFalse.GetTagType() == Node::TagType::Bool);

auto strFalse = document["StringFalse"];
CHECK(strFalse.GetTagType() == Node::TagType::Str);

auto localTag = document["LocalTag"];
CHECK(localTag.GetTagType() == Node::TagType::Unknown);
}
26 changes: 26 additions & 0 deletions src/AppInstallerSharedLib/AppInstallerStrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,32 @@ namespace AppInstaller::Utility
return result;
}

std::optional<std::wstring> TryConvertToUTF16(std::string_view input, UINT codePage)
{
if (input.empty())
{
return std::wstring{};
}

int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast<int>(input.length()), nullptr, 0);
if (utf16CharCount == 0)
{
return {};
}

// Since the string view should not contain the null char, the result won't either.
// This allows us to use the resulting size value directly in the string constructor.
std::wstring result(wil::safe_cast<size_t>(utf16CharCount), L'\0');

int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast<int>(input.length()), &result[0], wil::safe_cast<int>(result.size()));
if (utf16CharCount != utf16CharsWritten)
{
return {};
}

return std::optional{ result };
}

std::u32string ConvertToUTF32(std::string_view input)
{
if (input.empty())
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerSharedLib/Public/AppInstallerStrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ namespace AppInstaller::Utility
// Converts the given UTF8 string to UTF16
std::wstring ConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8);

// Tries to convert the given UTF8 string to UTF16
std::optional<std::wstring> TryConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8);

// Converts the given UTF8 string to UTF32
std::u32string ConvertToUTF32(std::string_view input);

Expand Down
42 changes: 40 additions & 2 deletions src/AppInstallerSharedLib/Public/winget/Yaml.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,26 @@ namespace AppInstaller::YAML
Mapping
};

Node() : m_type(Type::Invalid) {}
// The node's tag
enum class TagType
{
Unknown,
Null,
Bool,
Str,
Int,
Float,
Timestamp,
Seq,
Map,
};

Node() : m_type(Type::Invalid), m_tagType(TagType::Unknown) {}
Node(Type type, std::string tag, const Mark& mark);

// Sets the scalar value of the node.
void SetScalar(std::string value);
void SetScalar(std::string value, bool isQuoted);

// Adds a child node to the sequence.
template <typename... Args>
Expand All @@ -100,6 +115,7 @@ namespace AppInstaller::YAML
bool IsSequence() const { return m_type == Type::Sequence; }
bool IsMap() const { return m_type == Type::Mapping; }
Type GetType() const { return m_type; }
TagType GetTagType() const { return m_tagType; }

explicit operator bool() const { return IsDefined(); }

Expand All @@ -112,6 +128,18 @@ namespace AppInstaller::YAML
return as_dispatch(t);
}

template <typename T>
std::optional<T> try_as() const
{
if (m_type != Type::Scalar)
{
return {};
}

T* t = nullptr;
return try_as_dispatch(t);
}

bool operator<(const Node& other) const;

// Gets a child node from the mapping by its name.
Expand All @@ -135,20 +163,30 @@ namespace AppInstaller::YAML
const std::multimap<Node, Node>& Mapping() const;

private:
Node(std::string_view key) : m_type(Type::Scalar), m_scalar(key) {}
Node(std::string_view key) : m_type(Type::Scalar), m_scalar(key), m_tagType(TagType::Str) {}

// Require certain node types to; throwing if the requirement is not met.
void Require(Type type) const;

// The workers for the as function.
std::string as_dispatch(std::string*) const;
std::optional<std::string> try_as_dispatch(std::string*) const;

std::wstring as_dispatch(std::wstring*) const;
std::optional<std::wstring> try_as_dispatch(std::wstring*) const;

int64_t as_dispatch(int64_t*) const;
std::optional<int64_t> try_as_dispatch(int64_t*) const;

int as_dispatch(int*) const;
std::optional<int> try_as_dispatch(int*) const;

bool as_dispatch(bool*) const;
std::optional<bool> try_as_dispatch(bool*) const;

Type m_type;
std::string m_tag;
TagType m_tagType;
YAML::Mark m_mark;
std::string m_scalar;
std::optional<std::vector<Node>> m_sequence;
Expand Down
Loading