diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index a28081d341..95e800f82f 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1338,6 +1338,9 @@ RRF rrr rsop Rsp +rstringalnum +rstringalpha +rstringdigit Rstrtmgr RTB RTLREADING @@ -1352,6 +1355,7 @@ runtimeclass runtimeobject runtimepack runtimes +ruuid rvm rwin rwl diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.idl b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.idl index c6b86bfb77..041e3d1921 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.idl +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.idl @@ -15,6 +15,7 @@ namespace PowerRenameUI Windows.Foundation.Collections.IObservableVector SearchRegExShortcuts { get; }; Windows.Foundation.Collections.IObservableVector DateTimeShortcuts { get; }; Windows.Foundation.Collections.IObservableVector CounterShortcuts { get; }; + Windows.Foundation.Collections.IObservableVector RandomizerShortcuts { get; }; String OriginalCount; String RenamedCount; diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml index 83610f1a3b..d049a0d067 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml @@ -327,6 +327,8 @@ + + + + + + + + + + + + + + + + + + + @@ -508,6 +551,12 @@ Height="32" Content="" FontFamily="{ThemeResource SymbolThemeFontFamily}" /> + diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp index 1b8fe03b22..52b140c047 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp @@ -203,6 +203,12 @@ namespace winrt::PowerRenameUI::implementation m_CounterShortcuts.Append(winrt::make(L"${padding=8}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Padding").ValueAsString())); m_CounterShortcuts.Append(winrt::make(L"${increment=3,padding=4,start=900}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Complex").ValueAsString())); + m_RandomizerShortcuts = winrt::single_threaded_observable_vector(); + m_RandomizerShortcuts.Append(winrt::make(L"${rstringalnum=9}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Alnum").ValueAsString())); + m_RandomizerShortcuts.Append(winrt::make(L"${rstringalpha=13}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Alpha").ValueAsString())); + m_RandomizerShortcuts.Append(winrt::make(L"${rstringdigit=36}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Digit").ValueAsString())); + m_RandomizerShortcuts.Append(winrt::make(L"${ruuidv4}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Uuid").ValueAsString())); + InitializeComponent(); listView_ExplorerItems().ApplyTemplate(); @@ -283,6 +289,7 @@ namespace winrt::PowerRenameUI::implementation button_rename().IsEnabled(false); toggleButton_enumItems().IsChecked(true); + toggleButton_randItems().IsChecked(false); InitAutoComplete(); SearchReplaceChanged(); InvalidateItemListViewState(); @@ -749,6 +756,15 @@ namespace winrt::PowerRenameUI::implementation UpdateFlag(EnumerateItems, UpdateFlagCommand::Reset); }); + // CheckBox RandomizeItems + toggleButton_randItems().Checked([&](auto const&, auto const&) { + ValidateFlags(RandomizeItems); + UpdateFlag(RandomizeItems, UpdateFlagCommand::Set); + }); + toggleButton_randItems().Unchecked([&](auto const&, auto const&) { + UpdateFlag(RandomizeItems, UpdateFlagCommand::Reset); + }); + // ButtonSettings button_settings().Click([&](auto const&, auto const&) { OpenSettingsApp(); @@ -944,6 +960,10 @@ namespace winrt::PowerRenameUI::implementation { toggleButton_enumItems().IsChecked(true); } + if (flags & RandomizeItems) + { + toggleButton_randItems().IsChecked(true); + } if (flags & ExcludeFiles) { toggleButton_includeFiles().IsChecked(false); diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h index dc35a6b022..d1edf5b226 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h @@ -85,6 +85,7 @@ namespace winrt::PowerRenameUI::implementation winrt::Windows::Foundation::Collections::IObservableVector SearchRegExShortcuts() { return m_searchRegExShortcuts; } winrt::Windows::Foundation::Collections::IObservableVector DateTimeShortcuts() { return m_dateTimeShortcuts; } winrt::Windows::Foundation::Collections::IObservableVector CounterShortcuts() { return m_CounterShortcuts; } + winrt::Windows::Foundation::Collections::IObservableVector RandomizerShortcuts() { return m_RandomizerShortcuts; } hstring OriginalCount(); void OriginalCount(hstring value); @@ -107,6 +108,7 @@ namespace winrt::PowerRenameUI::implementation winrt::Windows::Foundation::Collections::IObservableVector m_searchRegExShortcuts; winrt::Windows::Foundation::Collections::IObservableVector m_dateTimeShortcuts; winrt::Windows::Foundation::Collections::IObservableVector m_CounterShortcuts; + winrt::Windows::Foundation::Collections::IObservableVector m_RandomizerShortcuts; // Used by PowerRenameManagerEvents HRESULT OnRename(_In_ IPowerRenameItem* renameItem); diff --git a/src/modules/powerrename/PowerRenameUILib/Strings/en-us/Resources.resw b/src/modules/powerrename/PowerRenameUILib/Strings/en-us/Resources.resw index a07c601c69..c67cc5af1a 100644 --- a/src/modules/powerrename/PowerRenameUILib/Strings/en-us/Resources.resw +++ b/src/modules/powerrename/PowerRenameUILib/Strings/en-us/Resources.resw @@ -151,7 +151,7 @@ Replace with - Replace using advanced counter syntax. + Replace using advanced counter syntax A simple counter that you can use anywhere in a replace string. @@ -295,10 +295,10 @@ Capitalize each word - Enumerate items + Enumeration features - Enumerate items + Enumeration features Select or deselect all @@ -363,4 +363,25 @@ Extension only - + + Random string with uppercase letters, lowercase letters and 0-9 digits, customized length. + + + Random string with uppercase letters and lowercase letters, customized length. + + + Random string with 0-9 digits, customized length. + + + Replace using random values + + + Random UUID according to v4 specification. + + + Random string features + + + Random string features + + \ No newline at end of file diff --git a/src/modules/powerrename/lib/Helpers.cpp b/src/modules/powerrename/lib/Helpers.cpp index 040167d6ef..aeadd8b683 100644 --- a/src/modules/powerrename/lib/Helpers.cpp +++ b/src/modules/powerrename/lib/Helpers.cpp @@ -661,3 +661,20 @@ bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime) } return false; } + +std::wstring CreateGuidStringWithoutBrackets() +{ + GUID guid; + if (CoCreateGuid(&guid) == S_OK) + { + OLECHAR* guidString; + if (StringFromCLSID(guid, &guidString) == S_OK) + { + std::wstring guidStr{ guidString }; + CoTaskMemFree(guidString); + return guidStr.substr(1, guidStr.length() - 2); + } + } + + return L""; +} \ No newline at end of file diff --git a/src/modules/powerrename/lib/Helpers.h b/src/modules/powerrename/lib/Helpers.h index dbe21ceef3..05b8eab19d 100644 --- a/src/modules/powerrename/lib/Helpers.h +++ b/src/modules/powerrename/lib/Helpers.h @@ -26,3 +26,4 @@ void SetRegNumber(const std::wstring& valueName, unsigned int value); bool GetRegBoolean(const std::wstring& valueName, bool defaultValue); void SetRegBoolean(const std::wstring& valueName, bool value); bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime); +std::wstring CreateGuidStringWithoutBrackets(); \ No newline at end of file diff --git a/src/modules/powerrename/lib/PowerRenameInterfaces.h b/src/modules/powerrename/lib/PowerRenameInterfaces.h index 974fee5425..c545e9dc00 100644 --- a/src/modules/powerrename/lib/PowerRenameInterfaces.h +++ b/src/modules/powerrename/lib/PowerRenameInterfaces.h @@ -17,7 +17,8 @@ enum PowerRenameFlags Uppercase = 0x200, Lowercase = 0x400, Titlecase = 0x800, - Capitalized = 0x1000 + Capitalized = 0x1000, + RandomizeItems = 0x2000 }; enum PowerRenameFilters @@ -150,3 +151,12 @@ public: (_In_ IEnumShellItems * enumShellItems) = 0; IFACEMETHOD(Cancel)() = 0; }; + +interface __declspec(uuid("FAB18E93-2E76-436B-8E26-B1240519AF12")) IPowerRenameRand : public IUnknown +{ +public: + IFACEMETHOD(Start) + (_In_ IEnumShellItems * enumShellItems) = 0; + IFACEMETHOD(Cancel) + () = 0; +}; \ No newline at end of file diff --git a/src/modules/powerrename/lib/PowerRenameLib.vcxproj b/src/modules/powerrename/lib/PowerRenameLib.vcxproj index 0f8c1e726d..d55c7b4b77 100644 --- a/src/modules/powerrename/lib/PowerRenameLib.vcxproj +++ b/src/modules/powerrename/lib/PowerRenameLib.vcxproj @@ -40,6 +40,7 @@ + @@ -56,6 +57,7 @@ + diff --git a/src/modules/powerrename/lib/PowerRenameRegEx.cpp b/src/modules/powerrename/lib/PowerRenameRegEx.cpp index 8fdeb67cf1..76136ff39c 100644 --- a/src/modules/powerrename/lib/PowerRenameRegEx.cpp +++ b/src/modules/powerrename/lib/PowerRenameRegEx.cpp @@ -130,23 +130,106 @@ IFACEMETHODIMP CPowerRenameRegEx::GetReplaceTerm(_Outptr_ PWSTR* replaceTerm) return hr; } -HRESULT CPowerRenameRegEx::_OnEnumerateItemsChanged() +HRESULT CPowerRenameRegEx::_OnEnumerateOrRandomizeItemsChanged() { m_enumerators.clear(); - const auto options = parseEnumOptions(m_RawReplaceTerm); - for (const auto e : options) - m_enumerators.emplace_back(e); + m_randomizer.clear(); + if (m_flags & RandomizeItems) + { + const auto options = parseRandomizerOptions(m_RawReplaceTerm); + + for (const auto& option : options) + { + m_randomizer.emplace_back(option); + } + } + + if (m_flags & EnumerateItems) + { + const auto options = parseEnumOptions(m_RawReplaceTerm); + for (const auto& option : options) + { + if (m_randomizer.end() == + std::find_if( + m_randomizer.begin(), + m_randomizer.end(), + [option](const Randomizer& r) -> bool { return r.options.replaceStrSpan.offset == option.replaceStrSpan.offset; } + )) + { + // Only add as enumerator if we didn't find a randomizer already at this offset. + // Every randomizer will also be a valid enumerator according to the definition of enumerators, which allows any string to mean the default enumerator, so it should be interpreted that the user wanted a randomizer if both were found at the same offset of the replace string. + m_enumerators.emplace_back(option); + } + } + } + + m_replaceWithRandomizerOffsets.clear(); m_replaceWithEnumeratorOffsets.clear(); + + int32_t offset = 0; + int ei = 0; // Enumerators index + int ri = 0; // Randomizer index + std::wstring replaceWith{ m_RawReplaceTerm }; // Remove counter expressions and calculate their offsets in replaceWith string. - int32_t offset = 0; - for (const auto& e : options) + + if ((m_flags & EnumerateItems) && (m_flags & RandomizeItems)) { - replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length); - m_replaceWithEnumeratorOffsets.push_back(offset); - offset -= static_cast(e.replaceStrSpan.length); + // Both flags are on, we need to merge which ones should be applied. + while ((ei < m_enumerators.size()) && (ri < m_randomizer.size())) + { + const auto& e = m_enumerators[ei]; + const auto& r = m_randomizer[ri]; + if (e.replaceStrSpan.offset < r.options.replaceStrSpan.offset) + { + // if the enumerator is next in line, remove counter expression and calculate offset with it. + replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length); + m_replaceWithEnumeratorOffsets.push_back(offset); + offset -= static_cast(e.replaceStrSpan.length); + + ei++; + } + else + { + // if the randomizer is next in line, remove randomizer expression and calculate offset with it. + replaceWith.erase(r.options.replaceStrSpan.offset + offset, r.options.replaceStrSpan.length); + m_replaceWithRandomizerOffsets.push_back(offset); + offset -= static_cast(r.options.replaceStrSpan.length); + + ri++; + } + } } + + if (m_flags & EnumerateItems) + { + // Continue with all remaining enumerators + while (ei < m_enumerators.size()) + { + const auto& e = m_enumerators[ei]; + replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length); + m_replaceWithEnumeratorOffsets.push_back(offset); + offset -= static_cast(e.replaceStrSpan.length); + + ei++; + } + } + + if (m_flags & RandomizeItems) + { + // Continue with all remaining randomizer instances + while (ri < m_randomizer.size()) + { + const auto& r = m_randomizer[ri]; + replaceWith.erase(r.options.replaceStrSpan.offset + offset, r.options.replaceStrSpan.length); + m_replaceWithRandomizerOffsets.push_back(offset); + offset -= static_cast(r.options.replaceStrSpan.length); + + ri++; + } + } + return SHStrDup(replaceWith.data(), &m_replaceTerm); } @@ -163,8 +246,8 @@ IFACEMETHODIMP CPowerRenameRegEx::PutReplaceTerm(_In_ PCWSTR replaceTerm, bool f CoTaskMemFree(m_replaceTerm); m_RawReplaceTerm = replaceTerm; - if (m_flags & EnumerateItems) - hr = _OnEnumerateItemsChanged(); + if ((m_flags & RandomizeItems) || (m_flags & EnumerateItems)) + hr = _OnEnumerateOrRandomizeItemsChanged(); else hr = SHStrDup(replaceTerm, &m_replaceTerm); } @@ -189,13 +272,20 @@ IFACEMETHODIMP CPowerRenameRegEx::PutFlags(_In_ DWORD flags) if (m_flags != flags) { const bool newEnumerate = flags & EnumerateItems; - const bool refreshReplaceTerm = !!(m_flags & EnumerateItems) != newEnumerate; + const bool newRandomizer = flags & RandomizeItems; + const bool refreshReplaceTerm = + (!!(m_flags & EnumerateItems) != newEnumerate) || + (!!(m_flags & RandomizeItems) != newRandomizer); + m_flags = flags; + if (refreshReplaceTerm) { CSRWExclusiveAutoLock lock(&m_lock); - if (newEnumerate) - _OnEnumerateItemsChanged(); + if (newEnumerate || newRandomizer) + { + _OnEnumerateOrRandomizeItemsChanged(); + } else { CoTaskMemFree(m_replaceTerm); @@ -325,17 +415,75 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, u static const std::wregex zeroGroupRegex(L"(([^\\$]|^)(\\$\\$)*)\\$[0]"); static const std::wregex otherGroupsRegex(L"(([^\\$]|^)(\\$\\$)*)\\$([1-9])"); - if (m_flags & EnumerateItems) + if ((m_flags & EnumerateItems) || (m_flags & RandomizeItems)) { + int ei = 0; // Enumerators index + int ri = 0; // Randomizer index std::array buffer; int32_t offset = 0; - for (size_t ei = 0; ei < m_enumerators.size(); ++ei) + if ((m_flags & EnumerateItems) && (m_flags & RandomizeItems)) { - const auto& e = m_enumerators[ei]; - const auto replacementLength = static_cast(e.printTo(buffer.data(), buffer.size(), enumIndex)); - replaceTerm.insert(e.replaceStrSpan.offset + offset + m_replaceWithEnumeratorOffsets[ei], buffer.data()); - offset += replacementLength; + // Both flags are on, we need to merge which ones should be applied. + while ((ei < m_enumerators.size()) && (ri < m_randomizer.size())) + { + const auto& e = m_enumerators[ei]; + const auto& r = m_randomizer[ri]; + if (e.replaceStrSpan.offset < r.options.replaceStrSpan.offset) + { + // if the enumerator is next in line, apply it. + const auto replacementLength = static_cast(e.printTo(buffer.data(), buffer.size(), enumIndex)); + replaceTerm.insert(e.replaceStrSpan.offset + offset + m_replaceWithEnumeratorOffsets[ei], buffer.data()); + offset += replacementLength; + + ei++; + } + else + { + // if the randomizer is next in line, apply it. + std::string randomValue = r.randomize(); + std::wstring wRandomValue(randomValue.begin(), randomValue.end()); + replaceTerm.insert(r.options.replaceStrSpan.offset + offset + m_replaceWithRandomizerOffsets[ri], wRandomValue); + offset += static_cast(wRandomValue.length()); + + if (e.replaceStrSpan.offset == r.options.replaceStrSpan.offset) + { + // In theory, this shouldn't happen here as we were guarding against it when filling the randomizer and enumerator structures, but it's still here as a fail safe. + // Every randomizer will also be a valid enumerator according to the definition of enumerators, which allow any string to mean the default enumerator, so it should be interpreted that the user wanted a randomizer if both were found at the same offset of the replace string. + ei++; + } + + ri++; + } + } + } + + if (m_flags & EnumerateItems) + { + // Replace all remaining enumerators + while (ei < m_enumerators.size()) + { + const auto& e = m_enumerators[ei]; + const auto replacementLength = static_cast(e.printTo(buffer.data(), buffer.size(), enumIndex)); + replaceTerm.insert(e.replaceStrSpan.offset + offset + m_replaceWithEnumeratorOffsets[ei], buffer.data()); + offset += replacementLength; + + ei++; + } + } + if (m_flags & RandomizeItems) + { + // Replace all remaining randomizer instances + while (ri < m_randomizer.size()) + { + const auto& r = m_randomizer[ri]; + std::string randomValue = r.randomize(); + std::wstring wRandomValue(randomValue.begin(), randomValue.end()); + replaceTerm.insert(r.options.replaceStrSpan.offset + offset + m_replaceWithRandomizerOffsets[ri], wRandomValue); + offset += static_cast(wRandomValue.length()); + + ri++; + } } } diff --git a/src/modules/powerrename/lib/PowerRenameRegEx.h b/src/modules/powerrename/lib/PowerRenameRegEx.h index 1b4e20a713..55c6c14c17 100644 --- a/src/modules/powerrename/lib/PowerRenameRegEx.h +++ b/src/modules/powerrename/lib/PowerRenameRegEx.h @@ -3,6 +3,9 @@ #include "srwlock.h" #include "Enumerating.h" + +#include "Randomizer.h" + #include "PowerRenameInterfaces.h" #define DEFAULT_FLAGS 0 @@ -38,7 +41,7 @@ protected: void _OnReplaceTermChanged(); void _OnFlagsChanged(); void _OnFileTimeChanged(); - HRESULT _OnEnumerateItemsChanged(); + HRESULT _OnEnumerateOrRandomizeItemsChanged(); size_t _Find(std::wstring data, std::wstring toSearch, bool caseInsensitive, size_t pos); @@ -59,6 +62,9 @@ protected: std::vector m_enumerators; std::vector m_replaceWithEnumeratorOffsets; + std::vector m_randomizer; + std::vector m_replaceWithRandomizerOffsets; + struct RENAME_REGEX_EVENT { IPowerRenameRegExEvents* pEvents; diff --git a/src/modules/powerrename/lib/Randomizer.cpp b/src/modules/powerrename/lib/Randomizer.cpp new file mode 100644 index 0000000000..045f1cfdeb --- /dev/null +++ b/src/modules/powerrename/lib/Randomizer.cpp @@ -0,0 +1,55 @@ +#include "pch.h" + +#include "Randomizer.h" + +std::vector parseRandomizerOptions(const std::wstring& replaceWith) +{ + static const std::wregex randAlnumRegex(LR"(rstringalnum=(\d+))"); + static const std::wregex randAlphaRegex(LR"(rstringalpha=(-?\d+))"); + static const std::wregex randDigitRegex(LR"(rstringdigit=(\d+))"); + static const std::wregex randUuidRegex(LR"(ruuidv4)"); + + std::string buf; + std::vector options; + std::wregex randGroupRegex(LR"(\$\{.*?\})"); + + for (std::wsregex_iterator i{ begin(replaceWith), end(replaceWith), randGroupRegex }, end; i != end; ++i) + { + std::wsmatch match = *i; + std::wstring matchString = match.str(); + + RandomizerOptions option; + option.replaceStrSpan.offset = match.position(); + option.replaceStrSpan.length = match.length(); + + std::wsmatch subMatch; + if (std::regex_search(matchString, subMatch, randAlnumRegex)) + { + int length = std::stoi(subMatch.str(1)); + option.alnum = true; + option.length = length; + } + if (std::regex_search(matchString, subMatch, randAlphaRegex)) + { + int length = std::stoi(subMatch.str(1)); + option.alpha = true; + option.length = length; + } + if (std::regex_search(matchString, subMatch, randDigitRegex)) + { + int length = std::stoi(subMatch.str(1)); + option.digit = true; + option.length = length; + } + if (std::regex_search(matchString, subMatch, randUuidRegex)) + { + option.uuid = true; + } + if (option.isValid()) + { + options.push_back(option); + } + } + + return options; +} diff --git a/src/modules/powerrename/lib/Randomizer.h b/src/modules/powerrename/lib/Randomizer.h new file mode 100644 index 0000000000..d157181802 --- /dev/null +++ b/src/modules/powerrename/lib/Randomizer.h @@ -0,0 +1,76 @@ +#pragma once + +#include "pch.h" + +#include "Helpers.h" + +#include + +struct ReplaceStrSpan +{ + size_t offset = 0; + size_t length = 0; +}; + +struct RandomizerOptions +{ + std::optional length; + std::optional alnum; + std::optional alpha; + std::optional digit; + std::optional uuid; + ReplaceStrSpan replaceStrSpan; + + bool isValid() const + { + return alnum.has_value() || alpha.has_value() || digit.has_value() || uuid.has_value(); + } +}; + +std::vector parseRandomizerOptions(const std::wstring& replaceWith); + +struct Randomizer +{ + RandomizerOptions options; + + inline Randomizer(RandomizerOptions opts) : + options(opts) {} + + std::string randomize() const + { + std::string chars; + + if (options.uuid.value_or(false)) + { + return unwide(CreateGuidStringWithoutBrackets()); + } + if (options.alnum.value_or(false)) + { + chars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + } + if (options.alpha.value_or(false)) + { + chars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + if (options.digit.value_or(false)) + { + chars += "0123456789"; + } + if (chars.empty()) + { + return ""; + } + + std::string result; + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution<> distribution(0, static_cast(chars.size()) - 1); + + for (int i = 0; i < options.length.value_or(10); ++i) + { + result += chars[distribution(generator)]; + } + + return result; + } +}; \ No newline at end of file diff --git a/src/modules/powerrename/lib/pch.h b/src/modules/powerrename/lib/pch.h index af9f76bbff..7a12102fbe 100644 --- a/src/modules/powerrename/lib/pch.h +++ b/src/modules/powerrename/lib/pch.h @@ -27,6 +27,7 @@ #include #include #include +#include #include diff --git a/src/modules/powerrename/unittests/CommonRegExTests.h b/src/modules/powerrename/unittests/CommonRegExTests.h index f9cb469fb3..b1b2b8d731 100644 --- a/src/modules/powerrename/unittests/CommonRegExTests.h +++ b/src/modules/powerrename/unittests/CommonRegExTests.h @@ -466,6 +466,151 @@ TEST_METHOD (VerifyCounterAllCustomizations) CoTaskMemFree(result); } +TEST_METHOD (VerifyRandomizerDefaultFlags) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = 0; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=9}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + Assert::AreEqual(L"foo$1bar_${rstringalnum=9}", result); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerNoRegex) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + Assert::AreEqual(L"foo$1bar_${}", result); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerNoRandomizerRegEx) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=9}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + Assert::AreEqual(L"foobar_${rstringalnum=9}", result); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerRegEx) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=9}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + std::wstring resultStr(result); + std::wregex pattern(L"foobar_\\w{9}"); + Assert::IsTrue(std::regex_match(resultStr, pattern)); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerRegExZeroValue) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=0}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + Assert::AreEqual(L"foobar_", result); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerRegExChar) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalpha=9}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + std::wstring resultStr(result); + std::wregex pattern(L"foobar_[A-Za-z]{9}"); + Assert::IsTrue(std::regex_match(resultStr, pattern)); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerRegExNum) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringdigit=9}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + std::wstring resultStr(result); + std::wregex pattern(L"foobar_\\d{9}"); + Assert::IsTrue(std::regex_match(resultStr, pattern)); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerRegExUuid) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${ruuidv4}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + std::wstring resultStr(result); + std::wregex pattern(L"foobar_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"); + Assert::IsTrue(std::regex_match(resultStr, pattern)); + CoTaskMemFree(result); +} + +TEST_METHOD (VerifyRandomizerRegExAllBackToBack) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = RandomizeItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=2}${rstringalpha=2}${rstringdigit=2}${ruuidv4}") == S_OK); + unsigned long index = {}; + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK); + std::wstring resultStr(result); + std::wregex pattern(L"foobar_\\w{2}[A-Za-z]{2}\\d{2}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"); + Assert::IsTrue(std::regex_match(resultStr, pattern)); + CoTaskMemFree(result); +} + #ifndef TESTS_PARTIAL }; }