[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
16 changed files with 584 additions and 26 deletions

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>