Repository: microsoft/terminal
File: src/cascadia/TerminalSettingsModel/MatchProfilesEntry.cpp (C++)

Code before:
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "MatchProfilesEntry.h"
#include "JsonUtils.h"

#include "MatchProfilesEntry.g.cpp"

using namespace Microsoft::Terminal::Settings::Model;

static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view CommandlineKey{ "commandline" };
static constexpr std::string_view SourceKey{ "source" };

namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
    MatchProfilesEntry::MatchProfilesEntry() noexcept :
        MatchProfilesEntryT<MatchProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::MatchProfiles)
    {
    }

    Json::Value MatchProfilesEntry::ToJson() const
    {
        auto json = NewTabMenuEntry::ToJson();

        JsonUtils::SetValueForKey(json, NameKey, _Name);
        JsonUtils::SetValueForKey(json, CommandlineKey, _Commandline);
        JsonUtils::SetValueForKey(json, SourceKey, _Source);

        return json;
    }

    winrt::com_ptr<NewTabMenuEntry> MatchProfilesEntry::FromJson(const Json::Value& json)
    {
        auto entry = winrt::make_self<MatchProfilesEntry>();

        JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
        JsonUtils::GetValueForKey(json, CommandlineKey, entry->_Commandline);
        JsonUtils::GetValueForKey(json, SourceKey, entry->_Source);

        return entry;
    }

    bool MatchProfilesEntry::MatchesProfile(const Model::Profile& profile)
    {
        // We use an optional here instead of a simple bool directly, since there is no
        // sensible default value for the desired semantics: the first property we want
        // to match on should always be applied (so one would set "true" as a default),
        // but if none of the properties are set, the default return value should be false
        // since this entry type is expected to behave like a positive match/whitelist.
        //
        // The easiest way to deal with this neatly is to use an optional, then for any
        // property to match we consider a null value to be "true", and for the return
        // value of the function we consider the null value to be "false".
        auto isMatching = std::optional<bool>{};

        if (!_Name.empty())
        {
            isMatching = { isMatching.value_or(true) && _Name == profile.Name() };
        }

        if (!_Source.empty())
        {
            isMatching = { isMatching.value_or(true) && _Source == profile.Source() };
        }

        if (!_Commandline.empty())
        {
            isMatching = { isMatching.value_or(true) && _Commandline == profile.Commandline() };
        }

        return isMatching.value_or(false);
    }

    Model::NewTabMenuEntry MatchProfilesEntry::Copy() const
    {
        auto entry = winrt::make_self<MatchProfilesEntry>();
        entry->_Name = _Name;
        entry->_Commandline = _Commandline;
        entry->_Source = _Source;
        return *entry;
    }
}


Review comment (refactor):
To make it easier on ourselves, we could write our own `unique_uregex` struct. This would allow us to define helper functions on it like `matches(std::wstring_view)` that does all of this under the hood for you.

Not sure if it's worth doing as part of this PR, but if you're feeling eager, I think it may be worth it.

Code after:
            return;                                                  \
        }                                                            \
        UErrorCode status = U_ZERO_ERROR;                            \
        _##name##Regex = til::ICU::CreateRegex(_##name, 0, &status); \
        if (U_FAILURE(status))                                       \
        {                                                            \
            _invalid##name = true;                                   \
            _##name##Regex.reset();                                  \
        }                                                            \
    }

    DEFINE_VALIDATE_FUNCTION(Name);
    DEFINE_VALIDATE_FUNCTION(Commandline);
    DEFINE_VALIDATE_FUNCTION(Source);

    bool MatchProfilesEntry::MatchesProfile(const Model::Profile& profile)
    {
        auto isMatch = [](const til::ICU::unique_uregex& regex, std::wstring_view text) {
            if (text.empty())
            {
                return false;
            }
            UErrorCode status = U_ZERO_ERROR;
            uregex_setText(regex.get(), reinterpret_cast<const UChar*>(text.data()), static_cast<int32_t>(text.size()), &status);
            const auto match = uregex_matches(regex.get(), 0, &status);
            return status == U_ZERO_ERROR && match;
        };

        if (!_Name.empty() && isMatch(_NameRegex, profile.Name()))
        {
            return true;
        }
        else if (!_Source.empty() && isMatch(_SourceRegex, profile.Source()))
        {
            return true;
        }
        else if (!_Commandline.empty() && isMatch(_CommandlineRegex, profile.Commandline()))
        {
            return true;
        }
        return false;
    }

    Model::NewTabMenuEntry MatchProfilesEntry::Copy() const
    {
        auto entry = winrt::make_self<MatchProfilesEntry>();
        entry->_Name = _Name;
        entry->_Commandline = _Commandline;
        entry->_Source = _Source;
        return *entry;
    }