Skip to content

Commit

Permalink
ENH: Add C++ script to add in-class {} member initializers to ITK
Browse files Browse the repository at this point in the history
The script that was used for the following pull requests:

pull request InsightSoftwareConsortium#3851
"STYLE: Add in-class `{}` member initializers to objects created by New()"

pull request InsightSoftwareConsortium#3913
"STYLE: Add in-class `{}` initializers to classes with virtual functions"

pull request InsightSoftwareConsortium#3917
"STYLE: Add in-class `{}` initializers to classes with override = default"
  • Loading branch information
N-Dekker committed Feb 23, 2023
1 parent 4728e7c commit dd4617d
Showing 1 changed file with 211 additions and 0 deletions.
211 changes: 211 additions & 0 deletions Utilities/Maintenance/AddEmptyDefaultMemberInitializers.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/

// Script to add in-class {} member initializers to ITK classes that have one or more virtual member function,
// itkNewMacro, itkSimpleNewMacro, or itkCreateAnotherMacro calls.
//
// 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 &&
line.find(" itk::SmartPointer") == 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;
}

0 comments on commit dd4617d

Please sign in to comment.