mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[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:
parent
1ae8327a43
commit
c148b51698
4
.github/actions/spell-check/expect.txt
vendored
4
.github/actions/spell-check/expect.txt
vendored
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<ToggleButton
|
||||
x:Name="toggleButton_randItems"
|
||||
x:Uid="ToggleButton_RandItems"
|
||||
Height="32"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
@ -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"";
|
||||
}
|
@ -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();
|
@ -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;
|
||||
};
|
@ -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">
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
55
src/modules/powerrename/lib/Randomizer.cpp
Normal file
55
src/modules/powerrename/lib/Randomizer.cpp
Normal 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;
|
||||
}
|
76
src/modules/powerrename/lib/Randomizer.h
Normal file
76
src/modules/powerrename/lib/Randomizer.h
Normal 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;
|
||||
}
|
||||
};
|
@ -27,6 +27,7 @@
|
||||
#include <variant>
|
||||
#include <charconv>
|
||||
#include <string>
|
||||
#include <random>
|
||||
|
||||
#include <ProjectTelemetry.h>
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user