Skip to content

Commit

Permalink
Add support for bool, strings and arrays in Configuration settings (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
msftrubengu committed Apr 10, 2023
1 parent 3dac77f commit 6f0bf7b
Show file tree
Hide file tree
Showing 16 changed files with 810 additions and 35 deletions.
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"))
{
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
3 changes: 3 additions & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
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

0 comments on commit 6f0bf7b

Please sign in to comment.