I wrote a simple C flashcard program; it takes a filename as its second argument and this file is then read line-by-line and using ncurses, the question is shown to the user, on pressing enter she is shown the answer, pressing again refreshes the ncurses window so that the next line is read. The file that is read is a simple TSV (though I've written it so that it can be any delimiter). Here's the file, titled f.c:
Note: I used readline(), a non-C-standard function, because this is a personal project that I'll only ever be using on POSIX systems, so the headache of properly managing memory with fgets just wasn't worth it
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#include <errno.h>
#define DELIM "\t"
int count_occ(char* string, char* val)
{
int count = 0;
const char* tmp = string;
while(tmp = strstr(tmp, val))
{
count++;
tmp++;
}
return count;
}
bool valid_file(FILE* stream)
{
char* line = NULL;
size_t len;
while(getline(&line, &len, stream) != -1)
{
if(count_occ(line, DELIM) != 1)
{
return false;
}
}
return true;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
fprintf(stderr, "File name must be supplied\n");
exit(EXIT_FAILURE);
}
if(!access(argv[1], F_OK) == 0)
{
fprintf(stderr, "File supplied in second argument does not exist\n");
exit(EXIT_FAILURE);
}
FILE* stream = fopen(argv[1], "r");
if(!valid_file(stream))
{
fprintf(stderr, "File supplied is not properly formatted: a line is missing a set of delimited values or too many fields were noticed\n");
exit(EXIT_FAILURE);
}
initscr();
noecho();
raw();
char* line = NULL;
size_t len;
while(getline(&line, &len, stream) != -1)
{
char* qn = strtok(line, DELIM);
char* ans = strtok(NULL, DELIM);
char c;
printw("Question:\n\t%s\nAnswer:\n\t...\n\n", qn);
while((c = getch()) != 10);
refresh();
move(0, 0);
printw("Question:\n\t%s\nAnswer:\n\t%s\n\n", qn, ans);
while((c = getch()) != 10);
refresh();
move(0, 0);
}
endwin();
return 0;
}
I compiled it using:
gcc f.c -o fc -lncurses
..with the correct libraries installed, of course.
I then used this file, test.txt, as a sample:
What is my name? Teddy Roosevolt
What is my name? Teddy Roosevolt
Where am I from? USA
What is my ethnicity? Caucasian
What is my nationality? British
What is my name? Teddy Roosevolt
Where am I from? USA
What is my ethnicity? Caucasian
What is my nationality? British
What is my name? Teddy Roosevolt
Where am I from? USA
What is my ethnicity? Caucasian
What is my nationality? British
Where am I from? USA
What is my ethnicity? Caucasian
What is my nationality? British
Then I ran it with:
./fc test.txt
And I got nothing - not even an error, not anything at all. Instead,the program ran immediately and exited straight away. I tested it out on poorly formatted files before I tested it out on error-less flashcard files - the poorly formatted ones raise all the right errors, which tells me that the reason it isn't working isn't to do with the block of code at the start of the main file checking whether the file is valid - I think it has to do with the ncurses part - but I can't tell what.
Thanks for your help.
Its quite simple really, and has nothing to do with ncurses:
You never rewind your file!
Your valid_file
function reads all the data in your file, leaving nothing for the loop in main
.
Since you never reset the file position after that function, the loop in main
is never executed as there is nothing for readline
left to read, and your program ends.
You must add fseek(stream, 0, SEEK_SET);
to the end of valid_file
just before return true
.
Also note, that you never free
the memory readline
allocates for you, which you must do after each call, and also reset the line
pointer to NULL and len
to 0, to get it to allocate a new buffer.
Otherwise you will end up with a memory leak.
It may not be noticeable if your text file is small, but this is still a bad programming practice!