PowerRename: Add Lookbehind (#7571)

* Add boost-regex library

* If enabled use boost lib for regex

Add property `_useBoostLib` to `CPowerRenameRegEx`. If enabled for
replacements with regular expressions the Boost Library is used instead
of the Standard Library.

* Extend signatures to create RegEx with Boost

Extend create and constructor singatures of `CPowerRenameRegEx` with an
option to enable (or disabled, which is default) the Boost Library.

* Verify Lookbehind fails with STD library

To verify that the boost library is disabled as expected, check if a
lookbehind fails.

* Add Unit tests for RegEx with Boost

Add unit tests to verify regex replacement with Boost Library. They are
copied and adapted from the Standard Library tests.

* Improve verify capturing groups test with Boost

It is possible to use a capturing group followed by numbers as
replacement if the group number is enclosed in curly braces.
Added test cases based on the Standard Library tests.

* Add useBoostLib to settings interface

* Get library option from settings object

* Reduce signatures of RegEx by "useBoost"

Remove the parameter added in 19105cf, as it became obsolete.

* Settings: Read useBoostLib from JSON file

* Add UseBoostLib Option to UI

* Boost Lib label states the regex syntax difference

* Fix Regex with Boost Lib tests

- Do not load settings another time in CPowerRenameRegEx ctor
- Set flag correctly in standard library regex tests

* Add "lookbehind" to dictionary

* change Library to lowercase, and also add a comment

As suggested by @enricogior.

Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com>

* Change Library to lowercase and add a comment

 As suggested by @enricogior.

Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com>
This commit is contained in:
Raphael Horber
2020-11-09 19:13:43 +01:00
committed by GitHub
parent 5a9e1b14cb
commit 8c7f2b6a74
27 changed files with 602 additions and 8 deletions

View File

@@ -146,6 +146,8 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\boost.1.72.0.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -153,5 +155,7 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.72.0.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets'))" />
</Target>
</Project>

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="boost" version="1.72.0.0" targetFramework="native" />
<package id="boost_regex-vc142" version="1.72.0.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>

View File

@@ -218,6 +218,8 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\boost.1.72.0.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -225,5 +227,7 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.72.0.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets'))" />
</Target>
</Project>

View File

@@ -144,4 +144,8 @@
<data name="Extended_Menu_Info" xml:space="preserve">
<value>Only show the PowerRename menu item on the extended context menu (Shift + Right-click).</value>
</data>
</root>
<data name="Use_Boost_Lib" xml:space="preserve">
<value>Use Boost library (provides extended features but may use different regex syntax).</value>
<comment>Boost is a product name, should not be translated</comment>
</data>
</root>

View File

@@ -234,6 +234,11 @@ public:
GET_RESOURCE_STRING(IDS_EXTENDED_MENU_INFO),
CSettingsInstance().GetExtendedContextMenuOnly());
settings.add_bool_toggle(
L"bool_use_boost_lib",
GET_RESOURCE_STRING(IDS_USE_BOOST_LIB),
CSettingsInstance().GetUseBoostLib());
return settings.serialize_to_buffer(buffer, buffer_size);
}
@@ -252,6 +257,7 @@ public:
CSettingsInstance().SetMaxMRUSize(values.get_int_value(L"int_max_mru_size").value());
CSettingsInstance().SetShowIconOnMenu(values.get_bool_value(L"bool_show_icon_on_menu").value());
CSettingsInstance().SetExtendedContextMenuOnly(values.get_bool_value(L"bool_show_extended_menu").value());
CSettingsInstance().SetUseBoostLib(values.get_bool_value(L"bool_use_boost_lib").value());
CSettingsInstance().Save();
Trace::SettingsChanged();

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="boost" version="1.72.0.0" targetFramework="native" />
<package id="boost_regex-vc142" version="1.72.0.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>

View File

@@ -189,6 +189,8 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\boost.1.72.0.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -196,5 +198,7 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.72.0.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets'))" />
</Target>
</Project>

View File

@@ -1,8 +1,10 @@
#include "pch.h"
#include "PowerRenameRegEx.h"
#include "Settings.h"
#include <regex>
#include <string>
#include <algorithm>
#include <boost/regex.hpp>
using namespace std;
@@ -177,6 +179,8 @@ CPowerRenameRegEx::CPowerRenameRegEx() :
// Init to empty strings
SHStrDup(L"", &m_searchTerm);
SHStrDup(L"", &m_replaceTerm);
_useBoostLib = CSettingsInstance().GetUseBoostLib();
}
CPowerRenameRegEx::~CPowerRenameRegEx()
@@ -206,14 +210,29 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result)
if (m_flags & UseRegularExpressions)
{
std::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? regex_constants::icase | regex_constants::ECMAScript : regex_constants::ECMAScript);
if (m_flags & MatchAllOccurences)
if (_useBoostLib)
{
res = regex_replace(wstring(source), pattern, replaceTerm);
boost::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? boost::regex::icase | boost::regex::ECMAScript : boost::regex::ECMAScript);
if (m_flags & MatchAllOccurences)
{
res = boost::regex_replace(wstring(source), pattern, replaceTerm);
}
else
{
res = boost::regex_replace(wstring(source), pattern, replaceTerm, boost::regex_constants::format_first_only);
}
}
else
{
res = regex_replace(wstring(source), pattern, replaceTerm, regex_constants::format_first_only);
std::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? regex_constants::icase | regex_constants::ECMAScript : regex_constants::ECMAScript);
if (m_flags & MatchAllOccurences)
{
res = regex_replace(wstring(source), pattern, replaceTerm);
}
else
{
res = regex_replace(wstring(source), pattern, replaceTerm, regex_constants::format_first_only);
}
}
}
else

View File

@@ -39,6 +39,7 @@ protected:
size_t _Find(std::wstring data, std::wstring toSearch, bool caseInsensitive, size_t pos);
bool _useBoostLib = false;
DWORD m_flags = DEFAULT_FLAGS;
PWSTR m_searchTerm = nullptr;
PWSTR m_replaceTerm = nullptr;

View File

@@ -31,6 +31,7 @@ namespace
const wchar_t c_mruEnabled[] = L"MRUEnabled";
const wchar_t c_mruList[] = L"MRUList";
const wchar_t c_insertionIdx[] = L"InsertionIdx";
const wchar_t c_useBoostLib[] = L"UseBoostLib";
unsigned int GetRegNumber(const std::wstring& valueName, unsigned int defaultValue)
{
@@ -414,6 +415,7 @@ void CSettings::Save()
jsonData.SetNamedValue(c_maxMRUSize, json::value(settings.maxMRUSize));
jsonData.SetNamedValue(c_searchText, json::value(settings.searchText));
jsonData.SetNamedValue(c_replaceText, json::value(settings.replaceText));
jsonData.SetNamedValue(c_useBoostLib, json::value(settings.useBoostLib));
json::to_file(jsonFilePath, jsonData);
GetSystemTimeAsFileTime(&lastLoadedTime);
@@ -457,6 +459,7 @@ void CSettings::MigrateFromRegistry()
settings.flags = GetRegNumber(c_flags, 0);
settings.searchText = GetRegString(c_searchText, L"");
settings.replaceText = GetRegString(c_replaceText, L"");
settings.useBoostLib = false; // Never existed in registry, disabled by default.
}
void CSettings::ParseJson()
@@ -499,6 +502,10 @@ void CSettings::ParseJson()
{
settings.replaceText = jsonSettings.GetNamedString(c_replaceText);
}
if (json::has(jsonSettings, c_useBoostLib, json::JsonValueType::Boolean))
{
settings.useBoostLib = jsonSettings.GetNamedBoolean(c_useBoostLib);
}
}
catch (const winrt::hresult_error&) { }
}

View File

@@ -51,6 +51,16 @@ public:
settings.persistState = persistState;
}
inline bool GetUseBoostLib() const
{
return settings.useBoostLib;
}
inline void SetUseBoostLib(bool useBoostLib)
{
settings.useBoostLib = useBoostLib;
}
inline bool GetMRUEnabled() const
{
return settings.MRUEnabled;
@@ -114,6 +124,7 @@ private:
bool showIconOnMenu{ true };
bool extendedContextMenuOnly{ false }; // Disabled by default.
bool persistState{ true };
bool useBoostLib{ false }; // Disabled by default.
bool MRUEnabled{ true };
unsigned int maxMRUSize{ 10 };
unsigned int flags{ 0 };

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="boost" version="1.72.0.0" targetFramework="native" />
<package id="boost_regex-vc142" version="1.72.0.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>

View File

@@ -85,5 +85,6 @@ void Trace::SettingsChanged() noexcept
TraceLoggingBoolean(CSettingsInstance().GetPersistState(), "PersistState"),
TraceLoggingBoolean(CSettingsInstance().GetMRUEnabled(), "IsMRUEnabled"),
TraceLoggingUInt64(CSettingsInstance().GetMaxMRUSize(), "MaxMRUSize"),
TraceLoggingBoolean(CSettingsInstance().GetUseBoostLib(), "UseBoostLib"),
TraceLoggingUInt64(CSettingsInstance().GetFlags(), "Flags"));
}

View File

@@ -202,6 +202,8 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\boost.1.72.0.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -209,5 +211,7 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.72.0.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets'))" />
</Target>
</Project>

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="boost" version="1.72.0.0" targetFramework="native" />
<package id="boost_regex-vc142" version="1.72.0.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
@@ -196,6 +196,7 @@
<ClCompile Include="MockPowerRenameItem.cpp" />
<ClCompile Include="MockPowerRenameManagerEvents.cpp" />
<ClCompile Include="MockPowerRenameRegExEvents.cpp" />
<ClCompile Include="PowerRenameRegExBoostTests.cpp" />
<ClCompile Include="PowerRenameManagerTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
@@ -217,6 +218,8 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\boost.1.72.0.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -224,5 +227,7 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.72.0.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.72.0.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc142.1.72.0.0\build\boost_regex-vc142.targets'))" />
</Target>
</Project>

View File

@@ -8,6 +8,7 @@
<ClCompile Include="pch.cpp" />
<ClCompile Include="PowerRenameRegExTests.cpp" />
<ClCompile Include="TestFileHelper.cpp" />
<ClCompile Include="PowerRenameRegExBoostTests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="MockPowerRenameItem.h" />

View File

@@ -0,0 +1,437 @@
#include "pch.h"
#include "CppUnitTest.h"
#include "powerrename/lib/Settings.h"
#include <PowerRenameInterfaces.h>
#include <PowerRenameRegEx.h>
#include "MockPowerRenameRegExEvents.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace PowerRenameRegExBoostTests
{
struct SearchReplaceExpected
{
PCWSTR search;
PCWSTR replace;
PCWSTR test;
PCWSTR expected;
};
TEST_CLASS(SimpleTests)
{
public:
TEST_CLASS_INITIALIZE(ClassInitialize)
{
CSettingsInstance().SetUseBoostLib(true);
}
TEST_CLASS_CLEANUP(ClassCleanup)
{
CSettingsInstance().SetUseBoostLib(false);
}
TEST_METHOD(GeneralReplaceTest)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"foo") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"big") == S_OK);
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) == S_OK);
Assert::IsTrue(wcscmp(result, L"bigbar") == 0);
CoTaskMemFree(result);
}
TEST_METHOD(ReplaceNoMatch)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"notfound") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"big") == S_OK);
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) == S_OK);
Assert::IsTrue(wcscmp(result, L"foobar") == 0);
CoTaskMemFree(result);
}
TEST_METHOD(ReplaceNoSearchOrReplaceTerm)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) != S_OK);
Assert::IsTrue(result == nullptr);
CoTaskMemFree(result);
}
TEST_METHOD(ReplaceNoReplaceTerm)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"foo") == S_OK);
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) == S_OK);
Assert::IsTrue(wcscmp(result, L"bar") == 0);
CoTaskMemFree(result);
}
TEST_METHOD(ReplaceEmptyStringReplaceTerm)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"foo") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"") == S_OK);
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) == S_OK);
Assert::IsTrue(wcscmp(result, L"bar") == 0);
CoTaskMemFree(result);
}
TEST_METHOD(VerifyDefaultFlags)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = 0;
Assert::IsTrue(renameRegEx->GetFlags(&flags) == S_OK);
Assert::IsTrue(flags == MatchAllOccurences);
}
TEST_METHOD(VerifyCaseSensitiveSearch)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = CaseSensitive;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"Foo", L"Foo", L"FooBar", L"FooBar" },
{ L"Foo", L"boo", L"FooBar", L"booBar" },
{ L"Foo", L"boo", L"foobar", L"foobar" },
{ L"123", L"654", L"123456", L"654456" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceFirstOnly)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = 0;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"B", L"BB", L"ABA", L"ABBA" },
{ L"B", L"A", L"ABBBA", L"AABBA" },
{ L"B", L"BBB", L"ABABAB", L"ABBBABAB" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceAll)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurences;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"B", L"BB", L"ABA", L"ABBA" },
{ L"B", L"A", L"ABBBA", L"AAAAA" },
{ L"B", L"BBB", L"ABABAB", L"ABBBABBBABBB" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceAllCaseInsensitive)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurences | CaseSensitive;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"B", L"BB", L"ABA", L"ABBA" },
{ L"B", L"A", L"ABBBA", L"AAAAA" },
{ L"B", L"BBB", L"ABABAB", L"ABBBABBBABBB" },
{ L"b", L"BBB", L"AbABAb", L"ABBBABABBB" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceFirstOnlyUseRegEx)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"B", L"BB", L"ABA", L"ABBA" },
{ L"B", L"A", L"ABBBA", L"AABBA" },
{ L"B", L"BBB", L"ABABAB", L"ABBBABAB" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceAllUseRegEx)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurences | UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"B", L"BB", L"ABA", L"ABBA" },
{ L"B", L"A", L"ABBBA", L"AAAAA" },
{ L"B", L"BBB", L"ABABAB", L"ABBBABBBABBB" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceAllUseRegExCaseSensitive)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurences | UseRegularExpressions | CaseSensitive;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
{ L"B", L"BB", L"ABA", L"ABBA" },
{ L"B", L"A", L"ABBBA", L"AAAAA" },
{ L"b", L"BBB", L"AbABAb", L"ABBBABABBB" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyMatchAllWildcardUseRegEx)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurences | UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
// This differs from the Standard Library: .* has two matches (all and nothing).
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L".*", L"Foo", L"AAAAAA", L"FooFoo" },
{ L".+", L"Foo", L"AAAAAA", L"Foo" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
void VerifyReplaceFirstWildcard(SearchReplaceExpected sreTable[], int tableSize, DWORD flags)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
for (int i = 0; i < tableSize; i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::AreEqual(sreTable[i].expected, result);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyReplaceFirstWildCardUseRegex)
{
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L".*", L"Foo", L"AAAAAA", L"Foo" },
};
VerifyReplaceFirstWildcard(sreTable, ARRAYSIZE(sreTable), UseRegularExpressions);
}
TEST_METHOD(VerifyReplaceFirstWildCardUseRegexMatchAllOccurrences)
{
// This differs from the Standard Library: .* has two matches (all and nothing).
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L".*", L"Foo", L"AAAAAA", L"FooFoo" },
{ L".+", L"Foo", L"AAAAAA", L"Foo" },
};
VerifyReplaceFirstWildcard(sreTable, ARRAYSIZE(sreTable), UseRegularExpressions | MatchAllOccurences);
}
TEST_METHOD(VerifyReplaceFirstWildCardMatchAllOccurrences)
{
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L".*", L"Foo", L"AAAAAA", L"AAAAAA" },
{ L".*", L"Foo", L".*", L"Foo" },
{ L".*", L"Foo", L".*Bar.*", L"FooBarFoo" },
};
VerifyReplaceFirstWildcard(sreTable, ARRAYSIZE(sreTable), MatchAllOccurences);
}
TEST_METHOD(VerifyReplaceFirstWildNoFlags)
{
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L".*", L"Foo", L"AAAAAA", L"AAAAAA" },
{ L".*", L"Foo", L".*", L"Foo" },
};
VerifyReplaceFirstWildcard(sreTable, ARRAYSIZE(sreTable), 0);
}
TEST_METHOD(VerifyHandleCapturingGroups)
{
// This differs from the Standard Library: Boost does not recognize $123 as $1 and "23".
// To use a capturing group followed by numbers as replacement curly braces are needed.
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurences | UseRegularExpressions | CaseSensitive;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L"(foo)(bar)", L"$1_$002_$223_$001021_$00001", L"foobar", L"foo_$002__$001021_$00001" },
{ L"(foo)(bar)", L"$1_$002_${2}23_$001021_$00001", L"foobar", L"foo_$002_bar23_$001021_$00001" },
{ L"(foo)(bar)", L"_$1$2_$123$040", L"foobar", L"_foobar_$040" },
{ L"(foo)(bar)", L"_$1$2_${1}23$040", L"foobar", L"_foobar_foo23$040" },
{ L"(foo)(bar)", L"$$$1", L"foobar", L"$foo" },
{ L"(foo)(bar)", L"$$1", L"foobar", L"$1" },
{ L"(foo)(bar)", L"$12", L"foobar", L"" },
{ L"(foo)(bar)", L"${1}2", L"foobar", L"foo2" },
{ L"(foo)(bar)", L"$10", L"foobar", L"" },
{ L"(foo)(bar)", L"${1}0", L"foobar", L"foo0" },
{ L"(foo)(bar)", L"$01", L"foobar", L"$01" },
{ L"(foo)(bar)", L"$$$11", L"foobar", L"$" },
{ L"(foo)(bar)", L"$$${1}1", L"foobar", L"$foo1" },
{ L"(foo)(bar)", L"$$$$113a", L"foobar", L"$$113a" },
};
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyLookbehind)
{
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L"(?<=E12).*", L"Foo", L"AAE12BBB", L"AAE12Foo" },
{ L"(?<=E12).+", L"Foo", L"AAE12BBB", L"AAE12Foo" },
{ L"(?<=E\\d\\d).+", L"Foo", L"AAE12BBB", L"AAE12Foo" },
{ L"(?<!E12).*", L"Foo", L"AAE12BBB", L"Foo" },
{ L"(?<!E12).+", L"Foo", L"AAE12BBB", L"Foo" },
{ L"(?<!E\\d\\d).+", L"Foo", L"AAE12BBB", L"Foo" },
};
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
Assert::IsTrue(renameRegEx->PutFlags(UseRegularExpressions) == S_OK);
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK);
Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyEventsFire)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
CMockPowerRenameRegExEvents* mockEvents = new CMockPowerRenameRegExEvents();
CComPtr<IPowerRenameRegExEvents> regExEvents;
Assert::IsTrue(mockEvents->QueryInterface(IID_PPV_ARGS(&regExEvents)) == S_OK);
DWORD cookie = 0;
Assert::IsTrue(renameRegEx->Advise(regExEvents, &cookie) == S_OK);
DWORD flags = MatchAllOccurences | UseRegularExpressions | CaseSensitive;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
Assert::IsTrue(renameRegEx->PutSearchTerm(L"FOO") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"BAR") == S_OK);
Assert::IsTrue(lstrcmpi(L"FOO", mockEvents->m_searchTerm) == 0);
Assert::IsTrue(lstrcmpi(L"BAR", mockEvents->m_replaceTerm) == 0);
Assert::IsTrue(flags == mockEvents->m_flags);
Assert::IsTrue(renameRegEx->UnAdvise(cookie) == S_OK);
mockEvents->Release();
}
};
}

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "CppUnitTest.h"
#include "powerrename/lib/Settings.h"
#include <PowerRenameInterfaces.h>
#include <PowerRenameRegEx.h>
#include "MockPowerRenameRegExEvents.h"
@@ -18,8 +19,14 @@ namespace PowerRenameRegExTests
TEST_CLASS(SimpleTests){
public:
TEST_METHOD(GeneralReplaceTest){
CComPtr<IPowerRenameRegEx> renameRegEx;
TEST_CLASS_INITIALIZE(ClassInitialize)
{
CSettingsInstance().SetUseBoostLib(false);
}
TEST_METHOD(GeneralReplaceTest)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"foo") == S_OK);
@@ -362,6 +369,30 @@ TEST_METHOD(VerifyHandleCapturingGroups)
}
}
TEST_METHOD(VerifyLookbehindFails)
{
// Standard Library Regex Engine does not support lookbehind, thus test should fail.
SearchReplaceExpected sreTable[] = {
//search, replace, test, result
{ L"(?<=E12).*", L"Foo", L"AAAAAA", nullptr },
{ L"(?<!E12).*", L"Foo", L"AAAAAA", nullptr },
};
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
Assert::IsTrue(renameRegEx->PutFlags(UseRegularExpressions) == S_OK);
for (int i = 0; i < ARRAYSIZE(sreTable); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK);
Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == E_FAIL);
Assert::AreEqual(sreTable[i].expected, result);
CoTaskMemFree(result);
}
}
TEST_METHOD(VerifyEventsFire)
{
CComPtr<IPowerRenameRegEx> renameRegEx;

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="boost" version="1.72.0.0" targetFramework="native" />
<package id="boost_regex-vc142" version="1.72.0.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>