[PowerRename]Add random string values to file names (#32836)

* Add randomizer cheat sheet texts to UI tooltip

* Add randomizer icon (shuffle) + hint to main window

* Add randomizer logic + helpers, regex parsing

* Fix: remove unnecessary throw

* Fix: remove todo comment

* Fix: iffing logic

* Fix: add offset to randomizer onchange

* Update: guid generating to single function, handle bracket removing there

* Update: toggle off enum feat when random values are selected

* Update: main window UI tooltip texts to be more descriptive

* Update: remove unnecessary sstream include

* Fix: return empty string if chars has no value to avoid memory access violation

* Add unit tests

* Add PowerRename random string generating keywords

* Update: generating value names to be in line with POSIX conventions

* Allow to used with Enumerate at the same time

* Fix spellcheck

* Fix tests to take into account we no longer eat up empty expressions
with randomizer

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
Jaakko Hirvioja 2024-06-20 18:26:31 +03:00 committed by GitHub
parent 1ae8327a43
commit c148b51698
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 584 additions and 26 deletions

View File

@ -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

View File

@ -15,6 +15,7 @@ namespace PowerRenameUI
Windows.Foundation.Collections.IObservableVector<PatternSnippet> SearchRegExShortcuts { get; };
Windows.Foundation.Collections.IObservableVector<PatternSnippet> DateTimeShortcuts { get; };
Windows.Foundation.Collections.IObservableVector<PatternSnippet> CounterShortcuts { get; };
Windows.Foundation.Collections.IObservableVector<PatternSnippet> RandomizerShortcuts { get; };
String OriginalCount;
String RenamedCount;

View File

@ -327,6 +327,8 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock x:Uid="DateTimeCheatSheet_Title" FontWeight="SemiBold" />
<ListView
@ -407,6 +409,47 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock
x:Uid="RandomizerCheatSheet_Title"
Grid.Row="4"
Margin="0,10,0,0"
FontWeight="SemiBold" />
<ListView
Grid.Row="5"
Margin="-4,12,0,0"
IsItemClickEnabled="True"
ItemClick="DateTimeItemClick"
ItemsSource="{x:Bind RandomizerShortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PatternSnippet">
<Grid Margin="-10,0,0,0" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Padding="8"
HorizontalAlignment="Left"
Background="{ThemeResource ButtonBackground}"
BorderBrush="{ThemeResource ButtonBorderBrush}"
BorderThickness="1"
CornerRadius="4">
<TextBlock
FontFamily="Consolas"
Foreground="{ThemeResource ButtonForeground}"
Text="{x:Bind Code}" />
</Border>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Description}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Flyout>
</Button.Flyout>
@ -508,6 +551,12 @@
Height="32"
Content="&#xEA40;"
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
<ToggleButton
x:Name="toggleButton_randItems"
x:Uid="ToggleButton_RandItems"
Height="32"
Content="&#xE8B1;"
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
</StackPanel>
</StackPanel>

View File

@ -203,6 +203,12 @@ namespace winrt::PowerRenameUI::implementation
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${padding=8}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Padding").ValueAsString()));
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${increment=3,padding=4,start=900}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Complex").ValueAsString()));
m_RandomizerShortcuts = winrt::single_threaded_observable_vector<PowerRenameUI::PatternSnippet>();
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringalnum=9}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Alnum").ValueAsString()));
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringalpha=13}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Alpha").ValueAsString()));
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringdigit=36}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Digit").ValueAsString()));
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(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);

View File

@ -85,6 +85,7 @@ namespace winrt::PowerRenameUI::implementation
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> SearchRegExShortcuts() { return m_searchRegExShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> DateTimeShortcuts() { return m_dateTimeShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> CounterShortcuts() { return m_CounterShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> RandomizerShortcuts() { return m_RandomizerShortcuts; }
hstring OriginalCount();
void OriginalCount(hstring value);
@ -107,6 +108,7 @@ namespace winrt::PowerRenameUI::implementation
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_searchRegExShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_dateTimeShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_CounterShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_RandomizerShortcuts;
// Used by PowerRenameManagerEvents
HRESULT OnRename(_In_ IPowerRenameItem* renameItem);

View File

@ -151,7 +151,7 @@
<value>Replace with</value>
</data>
<data name="CounterCheatSheet_Title.Text" xml:space="preserve">
<value>Replace using advanced counter syntax.</value>
<value>Replace using advanced counter syntax</value>
</data>
<data name="CounterCheatSheet_Simple" xml:space="preserve">
<value>A simple counter that you can use anywhere in a replace string.</value>
@ -295,10 +295,10 @@
<value>Capitalize each word</value>
</data>
<data name="ToggleButton_EnumItems.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Enumerate items</value>
<value>Enumeration features</value>
</data>
<data name="ToggleButton_EnumItems.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Enumerate items</value>
<value>Enumeration features</value>
</data>
<data name="SelectAllCheckBox.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Select or deselect all</value>
@ -363,4 +363,25 @@
<data name="RenameParts_ExtensionOnly.Content" xml:space="preserve">
<value>Extension only</value>
</data>
</root>
<data name="RandomizerCheatSheet_Alnum" xml:space="preserve">
<value>Random string with uppercase letters, lowercase letters and 0-9 digits, customized length.</value>
</data>
<data name="RandomizerCheatSheet_Alpha" xml:space="preserve">
<value>Random string with uppercase letters and lowercase letters, customized length.</value>
</data>
<data name="RandomizerCheatSheet_Digit" xml:space="preserve">
<value>Random string with 0-9 digits, customized length.</value>
</data>
<data name="RandomizerCheatSheet_Title.Text" xml:space="preserve">
<value>Replace using random values</value>
</data>
<data name="RandomizerCheatSheet_Uuid" xml:space="preserve">
<value>Random UUID according to v4 specification.</value>
</data>
<data name="ToggleButton_RandItems.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Random string features</value>
</data>
<data name="ToggleButton_RandItems.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Random string features</value>
</data>
</root>

View File

@ -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"";
}

View File

@ -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();

View File

@ -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;
};

View File

@ -40,6 +40,7 @@
<ClInclude Include="PowerRenameManager.h" />
<ClInclude Include="PowerRenameMRU.h" />
<ClInclude Include="PowerRenameRegEx.h" />
<ClInclude Include="Randomizer.h" />
<ClInclude Include="Renaming.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="srwlock.h" />
@ -56,6 +57,7 @@
<ClCompile Include="PowerRenameManager.cpp" />
<ClCompile Include="PowerRenameMRU.cpp" />
<ClCompile Include="PowerRenameRegEx.cpp" />
<ClCompile Include="Randomizer.cpp" />
<ClCompile Include="Renaming.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="pch.cpp">

View File

@ -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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<wchar_t, MAX_PATH> 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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(wRandomValue.length());
ri++;
}
}
}

View File

@ -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<Enumerator> m_enumerators;
std::vector<int32_t> m_replaceWithEnumeratorOffsets;
std::vector<Randomizer> m_randomizer;
std::vector<int32_t> m_replaceWithRandomizerOffsets;
struct RENAME_REGEX_EVENT
{
IPowerRenameRegExEvents* pEvents;

View File

@ -0,0 +1,55 @@
#include "pch.h"
#include "Randomizer.h"
std::vector<RandomizerOptions> 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<RandomizerOptions> 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;
}

View File

@ -0,0 +1,76 @@
#pragma once
#include "pch.h"
#include "Helpers.h"
#include <common\utils\string_utils.h>
struct ReplaceStrSpan
{
size_t offset = 0;
size_t length = 0;
};
struct RandomizerOptions
{
std::optional<int> length;
std::optional<boolean> alnum;
std::optional<boolean> alpha;
std::optional<boolean> digit;
std::optional<boolean> uuid;
ReplaceStrSpan replaceStrSpan;
bool isValid() const
{
return alnum.has_value() || alpha.has_value() || digit.has_value() || uuid.has_value();
}
};
std::vector<RandomizerOptions> 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<int>(chars.size()) - 1);
for (int i = 0; i < options.length.value_or(10); ++i)
{
result += chars[distribution(generator)];
}
return result;
}
};

View File

@ -27,6 +27,7 @@
#include <variant>
#include <charconv>
#include <string>
#include <random>
#include <ProjectTelemetry.h>

View File

@ -466,6 +466,151 @@ TEST_METHOD (VerifyCounterAllCustomizations)
CoTaskMemFree(result);
}
TEST_METHOD (VerifyRandomizerDefaultFlags)
{
CComPtr<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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
};
}