Skip to content

Instantly share code, notes, and snippets.

@N-Dekker
Last active February 8, 2023 22:51
Show Gist options
  • Save N-Dekker/738d28b8d7528a32c12e7a129160b00f to your computer and use it in GitHub Desktop.
Save N-Dekker/738d28b8d7528a32c12e7a129160b00f to your computer and use it in GitHub Desktop.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// Script to add in-class {} member initializers to ITK classes that have one or more virtual member function,
// itkNewMacro, itkSimpleNewMacro, or itkCreateAnotherMacro calls.
//
// Only tested with Visual Studio 2019 (C++14).
//
// Needs to have the directory path to the ITK sources as command-line argument, for example:
//
// AddEmptyDefaultMemberInitializers.exe D:\src\ITK\Modules
//
// Initial version by Niels Dekker, LKEB, Leiden University Medical Center, 2023
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#include <cassert>
#include <cctype>
#include <cstring>
#include <deque>
#include <experimental/filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
using namespace std::experimental::filesystem::v1;
namespace
{
using Lines = std::deque<std::string>;
auto
ReadFile(const path & filePath)
{
Lines result;
std::ifstream inputFileStream{ filePath };
std::string line;
while (std::getline(inputFileStream, line))
{
result.push_back(line);
}
return result;
}
void
WriteFile(const path & filePath, const Lines & lines)
{
std::ofstream outputFileStream{ filePath };
for (const auto & line : lines)
{
outputFileStream << line << '\n';
}
}
template <unsigned int VLength>
bool
StringStartsWith(const std::string& str, const char (&substr)[VLength])
{
assert(substr[VLength - 1] == '\0');
return str.compare(0, VLength - 1, substr) == 0;
}
template <unsigned int VLength>
bool
StringEndsWith(const std::string & str, const char (&substr)[VLength])
{
assert(substr[VLength - 1] == '\0');
return str.size() >= VLength && str.compare(str.size() + 1 - VLength, VLength - 1, substr) == 0;
}
unsigned int
AddInitializers(Lines & lines)
{
auto numberOfAddedInitializers = 0u;
bool virtualMemberFunctionEncountered{ false };
for (auto & line : lines)
{
const auto lineStartsWith = [&line](const auto & substr) { return StringStartsWith(line, substr); };
const auto lineEndsWith = [&line](const auto & substr) { return StringEndsWith(line, substr); };
const auto numberOfChars = line.size();
if (numberOfChars > 0)
{
if (lineStartsWith(" itkNewMacro(") || lineStartsWith(" itkSimpleNewMacro(") ||
lineStartsWith(" itkCreateAnotherMacro(") || lineStartsWith(" virtual ") || lineEndsWith(" override") ||
lineEndsWith(" override;") || lineEndsWith(" override = default;"))
{
virtualMemberFunctionEncountered = true;
}
else
{
if (virtualMemberFunctionEncountered)
{
if (lineStartsWith("};"))
{
// Class definition ended here.
virtualMemberFunctionEncountered = false;
}
else
{
if (line.back() == ';' && lineStartsWith(" ") &&
line.find(" static ") == std::string::npos &&
line.find(" std::unique_ptr") == std::string::npos)
{
// The regular expression explicitly skips lines of code that have a `)` at the end, like:
// itkSetVectorMacro(NumberOfIterations, unsigned int, m_NumberOfLevels);
const std::regex expression{ R"( \w.* m_\w[^{}=]*[^{}=\)];)" };
// Regular expression that matches a C++ reference (`&`).
const std::regex expressionReference{ R"( \w.* & m_\w[^{}=]+;)" };
if (std::regex_match(line, expression) && !std::regex_match(line, expressionReference))
{
line.pop_back();
line.append("{};");
std::cout << line << '\n';
++numberOfAddedInitializers;
}
}
}
}
}
}
}
return numberOfAddedInitializers;
}
auto
ProcessFile(const path & filePath)
{
auto lines = ReadFile(filePath);
const auto numberOfAddedInitializers = AddInitializers(lines);
if (numberOfAddedInitializers > 0)
{
std::cout << "Added " << numberOfAddedInitializers << " initializer(s) to " << filePath.string() << std::endl;
WriteFile(filePath, lines);
}
return numberOfAddedInitializers;
}
void
ProcessDirectory(const path & directoryPath)
{
auto numberOfAddedInitializers = 0u;
for (const auto & entry : recursive_directory_iterator{ directoryPath })
{
const auto & path = entry.path();
const auto & extension = path.extension();
if ((!extension.empty()) && extension.string() == ".h" && StringStartsWith(path.stem().string(), "itk") &&
is_regular_file(path))
{
numberOfAddedInitializers += ProcessFile(path);
}
}
std::cout << "Added in total " << numberOfAddedInitializers << " initializer(s) to " << directoryPath.string()
<< std::endl;
}
} // namespace
int
main(int argc, char ** argv)
{
if (argc != 2)
{
std::cout << "Please specify the source directory path as command-line argument."
"\nNote: This program will modify the source files in-place!!!"
<< std::endl;
}
else
{
if (argv == nullptr)
{
return EXIT_FAILURE;
}
const char * const arg = argv[1];
if (arg == nullptr)
{
return EXIT_FAILURE;
}
ProcessDirectory(arg);
}
std::cout << "Press anything to continue" << std::endl;
std::cin.get();
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment