I am making a toy program to create classes from a string (so that e.g. I feed it "test1 test2" and it makes test1.cpp, test1.h, test2.cpp, test2.h)
The action happens in this function:
bool makeClassesFromString(std::string classNames){
bool returnValue = true;
if (classNames == ""){
returnValue = false;
}
else{
std::istringstream issClassNames (classNames);
std::string sClassName;
while(issClassNames.good()){
issClassNames >> sClassName;
std::string sourceName = sClassName;
sourceName += ".cpp";
std::string headerName = sClassName;
headerName += ".h";
std::ofstream source(sourceName.c_str()), std::ios::app);
std::ofstream header(headerName.c_str()), std::ios::app);
//source << "#include \"" << headerName << "\"";
source.close();
header.close();
}
}
return returnValue;
}
The files are opened in append mode to ensure that any classes that already exist aren't accidentally overwritten.
I recently returned to this program to include some extras - in particular, the old version just created two empty files and I wanted to modify it to create the files with the necessary defines and includes so I wouldn't have to manually go in and add them each time. This revealed unexpected behavior - the last class to be made gets any text added twice.
In the code sample above, I've commented out the line where I started working on this but when I don't comment it out, I get behavior like this:
input:
classmaker.exe test1 test2
output:
test1.h
test2.h
test1.cpp //(Which contains: #include "test1.h")
test2.cpp //(Which contains: #include "test2.h"#include "test2.h"
My guess is that my while(issClassNames.good())
returns an extra time with the last classname (and so appends the string to the source file again) but I am not certain what behavior drives this.
Proposed solutions so far:
- Test whether file already exists attempting to open in
ifstream
mode before opening inofstream
mode. (Problem: Doesn't solve issue of runningwhile
once mode than necessary) - Remove append mode. (Problem: Risks overwriting already existing class, still doesn't solve the while-loop issue)
- Use conditional test in while-loop, don't open files on last run by e.g. comparing current class name with last class name and aborting if equal. Problem: Not sure how to check whether this is last run except cludgy "is this the same class name as last time?" test which might also risk aborting too early if the input string is "test1 test1 test2"
EDIT
I have realized: It's not really that the istringstream.good() returns true once more than expected, it is rather that it evaluates like so:
input: "test1 test2"
evaluation:
good = true; extract next to string //(succeeds, overwrites empty string with "test1")
good = true; extract next to string //(succeeds, overwrites "test1" with "test2")
good = true; extract next to string //(fails, Doesn't overwrite "test2")
good = false; break loop.
EDIT 2 + 3
Well that was easily solved. Thank you, all of you. Unfortunately I can only mark one answer as accepted so I picked the first posted answer but I appreciate the help.
The final program in its entirety:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
void makeClassesFromStdInput();
void makeClassesFromParameter(int argc, const char** argv);
bool makeClassesFromString(std::string classNames);
int main(int argc, const char** argv){
switch (argc) {
case 0: //This shouldn’t happen
break;
case 1:
//This will keep making classes from the CLI until
//the user breaks the process.
makeClassesFromStdInput();
break;
default:
//This will make classes from the parameters passed
//to the program except the first parameter which is
//assumed to be the program name.
makeClassesFromParameter(argc, argv);
break;
}
return 0;
}
void makeClassesFromStdInput(){
std::cout << "Input class name and press enter.\nWhitespace delimits class names.\nA blank line ends the process.\n>";
bool running = true;
std::string classNames;
while(running){
std::getline(std::cin, classNames);
running = makeClassesFromString(classNames);
}
}
void makeClassesFromParameter(int argc, const char** argv){
std::string classNames = "";
for(int i = 1; i<argc; i++){
classNames += argv[i];
classNames += " ";
}
makeClassesFromString(classNames);
}
bool makeClassesFromString(std::string classNames){
bool returnValue = true;
if (classNames == ""){
returnValue = false;
}
else{
std::istringstream issClassNames (classNames);
std::string sClassName;
while(issClassNames >> sClassName){
std::string sourceName = sClassName;
sourceName += ".cpp";
std::string headerName = sClassName;
headerName += ".h";
std::ofstream source(sourceName.c_str(), std::ios::app);
std::ofstream header(headerName.c_str(), std::ios::app);
source << "#include \"" << headerName << "\"";
//Header is slightly more involved because we want all capital letters
for (std::string::size_type i=0; i<headerName.length(); ++i){
if(headerName[i] == '.'){
headerName[i] = '_';
}
else{
headerName[i] = toupper(headerName[i]);
}
}
header << "#ifndef __" << headerName << "\n"
<< "#define __" << headerName << "\n"
<< "\n"
<< "\n"
<< "#endif";
source.close();
header.close();
}
}
return returnValue;
}