Created
March 26, 2018 13:31
-
-
Save N-Dekker/a46cd003f19a84c6d285c2487fc5e13d to your computer and use it in GitHub Desktop.
The script used for "COMP: Moved ITK_DISALLOW_COPY_AND_ASSIGN calls to public section", http://review.source.kitware.com/#/c/23289/ on Visual C++ 2017
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Script to move ITK_DISALLOW_COPY_AND_ASSIGN calls to the public section | |
// of the classes. | |
// | |
// Niels Dekker, LKEB, Leiden University Medical center, 2018 | |
#include <cassert> | |
#include <cctype> | |
#include <deque> | |
#include <experimental/filesystem> | |
#include <fstream> | |
#include <iostream> | |
#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 << std::endl; | |
} | |
} | |
std::string StripClassName(std::string candidateClassName) | |
{ | |
for (auto& c : candidateClassName) | |
{ | |
if (c == ':') | |
{ | |
c = '\0'; | |
return candidateClassName.c_str(); | |
} | |
if (c == '<') | |
{ | |
c = '\0'; | |
return candidateClassName.c_str(); | |
} | |
if (!std::isalnum(c)) | |
{ | |
return {}; | |
} | |
} | |
return candidateClassName; | |
} | |
std::string FindClassName(const char* const line) | |
{ | |
std::istringstream inputStringStream{ line }; | |
std::string candidateClassName; | |
inputStringStream >> candidateClassName; | |
const char exportPostfix[] = "_EXPORT"; | |
if (candidateClassName.size() >= sizeof(exportPostfix) && candidateClassName.compare(candidateClassName.size() + 1 - sizeof(exportPostfix), sizeof(exportPostfix) - 1, exportPostfix) == 0) | |
{ | |
inputStringStream >> candidateClassName; | |
} | |
return StripClassName( candidateClassName ); | |
} | |
const char* GoToFirstNonSpace(const char* ptr) | |
{ | |
while (*ptr == ' ') | |
{ | |
++ptr; | |
} | |
return ptr; | |
} | |
template <unsigned N> | |
bool StringStartsWithPrefix(const char*& str, const char(&prefix)[N]) | |
{ | |
assert(prefix[N - 1] == '\0'); | |
if ((std::strlen(str) + 1 >= N) && (std::memcmp(str, prefix, N - 1) == 0)) | |
{ | |
str += N - 1; | |
return true; | |
} | |
return false; | |
} | |
struct Statistics | |
{ | |
unsigned numberOfClassNameMismatches; | |
unsigned numberOfDisallowMacroCalls; | |
unsigned numberOfMissingPublicSections; | |
unsigned numberOfSuccessfullMoves; | |
unsigned numberOfDisallowMacroCallsAlreadyAtRightPlace; | |
}; | |
Statistics ModifyLines(Lines& lines) | |
{ | |
Statistics statistics = {}; | |
auto className = std::string{}; | |
auto publicLineNumber = Lines::size_type{}; | |
for (auto lineNumber = Lines::size_type{}; lineNumber < lines.size(); ++lineNumber) | |
{ | |
const auto& line = lines[lineNumber]; | |
const auto numberOfChars = line.size(); | |
if (numberOfChars > 0) | |
{ | |
const char* c_str = GoToFirstNonSpace(line.c_str()); | |
if (StringStartsWithPrefix(c_str, "class") && (*c_str == ' ')) | |
{ | |
const auto newClassName = FindClassName(c_str); | |
if (!newClassName.empty()) | |
{ | |
className = newClassName; | |
publicLineNumber = 0; | |
} | |
} | |
else | |
{ | |
if ( (publicLineNumber == 0) && (!className.empty()) && StringStartsWithPrefix(c_str, "public")) | |
{ | |
if ((*GoToFirstNonSpace(c_str) == ':') && (*GoToFirstNonSpace(c_str + 1) == '\0')) | |
{ | |
// Found the first 'public' section of the class named 'className'. | |
publicLineNumber = lineNumber; | |
} | |
} | |
else | |
{ | |
if (StringStartsWithPrefix(c_str, "ITK_DISALLOW_COPY_AND_ASSIGN(")) | |
{ | |
++statistics.numberOfDisallowMacroCalls; | |
if (publicLineNumber > 0) | |
{ | |
assert(!className.empty()); | |
// Found a DISALLOW macro call, somewhere after a 'public' section of the class named 'className'. | |
const char c = *GoToFirstNonSpace(c_str); | |
if (c_str == (className + ");")) | |
{ | |
if (lineNumber == publicLineNumber + 1) | |
{ | |
std::cout << "Macro call already at the right place: " << lines[lineNumber] << std::endl; | |
++statistics.numberOfDisallowMacroCallsAlreadyAtRightPlace; | |
} | |
else | |
{ | |
{ | |
// Move the DISALLOW macro call up to the begin of the 'public' section. | |
std::string disallowMacroCall = std::move(lines[lineNumber]); | |
for (auto i = lineNumber; i > publicLineNumber + 1; --i) | |
{ | |
lines[i] = std::move(lines[i - 1]); | |
} | |
lines[publicLineNumber + 1] = std::move(disallowMacroCall); | |
} | |
if ((lines.size() > (lineNumber + 1)) && lines[lineNumber + 1].empty()) | |
{ | |
// Ensure that there is no empty line left below the original DISALLOW macro call location. | |
lines.erase(lines.begin() + lineNumber + 1); | |
} | |
if ((lines.size() > (lineNumber + 1)) && *GoToFirstNonSpace(lines[lineNumber + 1].c_str()) == '}') | |
{ | |
auto ptr = GoToFirstNonSpace(lines[lineNumber].c_str()); | |
if (StringStartsWithPrefix(ptr, "private")) | |
{ | |
if ((*GoToFirstNonSpace(ptr) == ':') && (*GoToFirstNonSpace(ptr + 1) == '\0')) | |
{ | |
// Erase empty private section. | |
lines.erase(lines.begin() + lineNumber); | |
if (lines[lineNumber - 1].empty()) | |
{ | |
// Erase empty line before the erased private section. | |
lines.erase(lines.begin() + lineNumber - 1); | |
} | |
} | |
} | |
} | |
if (!lines[publicLineNumber + 2].empty()) | |
{ | |
// Ensure that there is an empty line below the new DISALLOW macro call location. | |
lines.insert(lines.begin() + publicLineNumber + 2, std::string{}); | |
} | |
// Reset local variables for the next iteration. | |
className = std::string{}; | |
publicLineNumber = Lines::size_type{}; | |
++statistics.numberOfSuccessfullMoves; | |
} | |
} | |
else | |
{ | |
++statistics.numberOfClassNameMismatches; | |
std::cerr << "Mismatch! Class name: \"" << className << "\"; macro call: " << lines[lineNumber] << std::endl; | |
} | |
} | |
else | |
{ | |
++statistics.numberOfMissingPublicSections; | |
std::cerr << "No public section found for macro call " << lines[lineNumber] << std::endl; | |
} | |
} | |
} | |
} | |
} | |
} | |
return statistics; | |
} | |
auto ProcessFile(const path& filePath) | |
{ | |
auto lines = ReadFile(filePath); | |
const auto statistics = ModifyLines(lines); | |
if (statistics.numberOfSuccessfullMoves > 0) | |
{ | |
WriteFile(filePath, lines); | |
} | |
return statistics; | |
} | |
void ProcessDirectory(const path& directoryPath) | |
{ | |
Statistics statistics = {}; | |
const recursive_directory_iterator end; | |
unsigned numberOfModifiedFiles = 0; | |
for (recursive_directory_iterator it{ directoryPath }; it != end; ++it) | |
{ | |
const auto& path = it->path(); | |
const auto& extension = path.extension(); | |
if ( (!extension.empty()) && (extension.string() == ".h") && is_regular_file(path)) | |
{ | |
const auto statisticsPerFile = ProcessFile(path); | |
if (statisticsPerFile.numberOfDisallowMacroCalls > 0) | |
{ | |
numberOfModifiedFiles += (statisticsPerFile.numberOfSuccessfullMoves > 0) ? 1 : 0; | |
if ( (statisticsPerFile.numberOfDisallowMacroCalls > 1) || | |
(statisticsPerFile.numberOfDisallowMacroCalls != statisticsPerFile.numberOfSuccessfullMoves)) | |
std::cout | |
<< statisticsPerFile.numberOfDisallowMacroCalls | |
<< ' ' << statisticsPerFile.numberOfSuccessfullMoves | |
<< ' ' << statisticsPerFile.numberOfDisallowMacroCallsAlreadyAtRightPlace | |
<< ' ' << statisticsPerFile.numberOfClassNameMismatches | |
<< ' ' << statisticsPerFile.numberOfMissingPublicSections | |
<< ' ' << path << std::endl; | |
} | |
statistics.numberOfDisallowMacroCalls += statisticsPerFile.numberOfDisallowMacroCalls; | |
statistics.numberOfSuccessfullMoves += statisticsPerFile.numberOfSuccessfullMoves; | |
statistics.numberOfDisallowMacroCallsAlreadyAtRightPlace += statisticsPerFile.numberOfDisallowMacroCallsAlreadyAtRightPlace; | |
statistics.numberOfClassNameMismatches += statisticsPerFile.numberOfClassNameMismatches; | |
statistics.numberOfMissingPublicSections += statisticsPerFile.numberOfMissingPublicSections; | |
} | |
} | |
std::cout | |
<< "numberOfModifiedFiles:\t" << numberOfModifiedFiles | |
<< "\nDisallowMacroCalls:\t" << statistics.numberOfDisallowMacroCalls | |
<< "\nSuccessfullMoves:\t" << statistics.numberOfSuccessfullMoves | |
<< "\nDisallowMacroCallsAlreadyAtRightPlace:\t" << statistics.numberOfDisallowMacroCallsAlreadyAtRightPlace | |
<< "\nClassNameMismatches:\t" << statistics.numberOfClassNameMismatches | |
<< "\nMissingPublicSections:\t" << statistics.numberOfMissingPublicSections | |
<< std::endl; | |
} | |
} | |
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