diff --git a/Utilities/Maintenance/AddEmptyDefaultMemberInitializers.cxx b/Utilities/Maintenance/AddEmptyDefaultMemberInitializers.cxx new file mode 100644 index 00000000000..796d2f8fc68 --- /dev/null +++ b/Utilities/Maintenance/AddEmptyDefaultMemberInitializers.cxx @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::experimental::filesystem::v1; + +namespace +{ +using Lines = std::deque; + +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 +bool +StringStartsWith(const std::string & str, const char (&substr)[VLength]) +{ + assert(substr[VLength - 1] == '\0'); + return str.compare(0, VLength - 1, substr) == 0; +} + +template +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; +}