From a653f4daaa21556d100f94450af46312079bb13d Mon Sep 17 00:00:00 2001 From: dyx Date: Sat, 19 Nov 2022 18:31:55 +0800 Subject: [PATCH] - refactor to a single .c file - use longjmp() to handle errors - add memory-optimization and disk-optimization modes --- Makefile | 23 +- README.md | 17 +- apperr.c | 44 --- apperr.h | 28 -- applim.h | 12 - card.c | 381 ------------------ card.h | 32 -- ctab.c | 139 ------- ctab.h | 40 -- hardv.man1 => hardv.1.s | 20 +- hardv.c | 858 ++++++++++++++++++++++++++++++++++++++++ learn.c | 271 ------------- learn.h | 34 -- main.c | 145 ------- test/err/einvkey.ans | 2 +- test/err/elinesz.ans | 2 +- test/err/enokey.ans | 2 +- test/err/etimef.ans | 2 +- test/err/etimef2.ans | 2 +- test/err/etimef3.ans | 2 +- test/err/etimef4.ans | 2 +- test/err/etimef5.ans | 1 + test/err/etimef5.in | 13 + test/err/evalsz.ans | 2 +- test/err/mfield1.ans | 2 +- test/err/mfield2.ans | 2 +- test/err/mfield3.ans | 2 +- test/learn/4.act | 10 - test/learn/4.ans | 24 -- test/learn/4.arg | 0 test/learn/4.in | 19 - test/learn/4.now | 1 - version | 2 +- 33 files changed, 918 insertions(+), 1218 deletions(-) delete mode 100644 apperr.c delete mode 100644 apperr.h delete mode 100644 applim.h delete mode 100644 card.c delete mode 100644 card.h delete mode 100644 ctab.c delete mode 100644 ctab.h rename hardv.man1 => hardv.1.s (91%) create mode 100644 hardv.c delete mode 100644 learn.c delete mode 100644 learn.h delete mode 100644 main.c create mode 100644 test/err/etimef5.ans create mode 100644 test/err/etimef5.in delete mode 100644 test/learn/4.act delete mode 100644 test/learn/4.ans delete mode 100644 test/learn/4.arg delete mode 100644 test/learn/4.in delete mode 100644 test/learn/4.now diff --git a/Makefile b/Makefile index 514bf1d..8c5b406 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,23 @@ .PHONY: targets clean install test tag CC = cc +CFLAGS = -D_XOPEN_SOURCE=700 INSTALL = install prefix = /usr/local bindir = $(prefix)/bin datarootdir = $(prefix)/share mandir = $(datarootdir)/man -src = $(wildcard *.c) -deps = $(src:.c=.d) -objs = $(src:.c=.o) targets = hardv hardv.1 targets: $(targets) --include $(deps) - -hardv: $(objs) - $(CC) $(LDFLAGS) -o $@ $^ - -main.o: main.c version LICENSE +hardv: hardv.c $(CC) $(CFLAGS) \ -DVERSION="\"`cat version`\"" \ -DCOPYRT="\"`grep -i 'copyright (c)' LICENSE`\"" \ - -o $@ -c $< - -%.o: %.c - $(CC) $(CFLAGS) -o $@ -c $< - -%.d: %.c - @$(CC) -MM $< | sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' >$@ + -o $@ $^ -hardv.1: hardv.man1 version LICENSE +hardv.1: hardv.1.s version LICENSE @echo building manpage... @set -e; \ cp $< $@; \ @@ -54,7 +41,7 @@ test: $(targets) echo all tests passed; clean: - @rm -rf $(targets) $(deps) $(objs) *.tmp + @rm -rf $(targets) *.tmp @for i in test/*; do \ cd $$i; \ ./clean; \ diff --git a/README.md b/README.md index ab89086..370192d 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ hardv ===== (/*hɑ:rd'vi:*/) -`hardv` is a CLI flashcard app for UNIX-compatible systems, -conforming to the UNIX philosophy. +`hardv` is a CLI flashcard app for Unix-compatible systems, +conforming to the Unix philosophy. - Almost everything can be customized, with any programming language you prefer. @@ -23,16 +23,25 @@ E.g.: and more. - The format of input files are -easy to be parsed by both human and other UNIX utilities +easy to be parsed by both human and other Unix utilities like `grep`/`cut`/`sed`/`awk`/... - Metadata like scheduled time is written back to input files; Thus all your data is in files created and managed by yourself; Nothing would prevent you from migration or uninstall. -- It is a UNIX filter, although an interactive one. +- It is a Unix filter, although an interactive one. That makes `hardv` easy to be called by other programs. +By default, `hardv` runs in the memory-optimization mode. +Only one card is loaded in the memory at the same time. +The process consumes about 512KB to 1MB memory +for cards with typical size in common systems. +However, `hardv` allows you to turn it into the disk-optimization mode. +The disk-optimization mode consumes less disk I/O, +at the cost of loading all cards of a file into the memory. +See the `-d` option in the man page `hardv(1)`. + Getting Started --------------- diff --git a/apperr.c b/apperr.c deleted file mode 100644 index dbd146c..0000000 --- a/apperr.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include "apperr.h" - -int apperr; - -char *aestr(int eno) -{ - static char *msg[] = { - NULL, - NULL, - "too many fields", - "mandaroty field not found", - "invalid time format", - "field key too large", - "feild value too large", - "filed key is expected", - "line too large", - "field value is expected", - "duplicated key", - "too many lines", - "too many cards", - "invalid function arguments", - "file name too large", - "fail to create backup file", - "invalid field key", - "backup file already exists" - }; - - if (eno == AESYS) - return strerror(errno); - if (eno < 2 || eno >= sizeof msg / sizeof msg[0]) - return "undefined application error"; - return msg[eno]; -} - -void aeprint(char *head) -{ - if (head) - fprintf(stderr, "%s: %s\n", head, aestr(apperr)); - else - fprintf(stderr, " %s\n", aestr(apperr)); -} diff --git a/apperr.h b/apperr.h deleted file mode 100644 index 3d60d00..0000000 --- a/apperr.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef APPERR_H -#define APPERR_H - -extern int apperr; -char *aestr(int eno); -void aeprint(char *head); - -enum { - AESYS = 1, /* system error */ - AENFIELD, /* too many fields */ - AEMFIELD, /* mandatory field not found */ - AETIMEF, /* invalid time format */ - AEKEYSZ, /* field key too large */ - AEVALSZ, /* feild val too large */ - AENOKEY, /* filed key is expected */ - AELINESZ, /* line too large */ - AENOVAL, /* field value is expected */ - AEDUPKEY, /* duplicated key */ - AENLINE, /* too many lines */ - AENCARD, /* too many cards */ - AEINVAL, /* invalid function arguments */ - AEPATHSZ, /* file name too large */ - AEBACKUP, /* fail to create backup file */ - AEINVKEY, /* invalid file key */ - AEEXBF /* backup file already exists */ -}; - -#endif diff --git a/applim.h b/applim.h deleted file mode 100644 index 03dea5d..0000000 --- a/applim.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef APPLIM_H -#define APPLIM_H - -#define PATHSZ 32767 -#define NLINE 32767 -#define LINESZ 32767 -#define NCARD 32767 -#define NFIELD 16 -#define KEYSZ 8 -#define VALSZ 32767 - -#endif diff --git a/card.c b/card.c deleted file mode 100644 index fdd029b..0000000 --- a/card.c +++ /dev/null @@ -1,381 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "apperr.h" -#include "card.h" -#define MOD "MOD" -#define QUES "Q" -#define ANSW "A" -#define PREV "PREV" -#define NEXT "NEXT" -#define TIMEFMT "%Y-%m-%d %H:%M:%S" -#define TIMEFMT_P "%d-%d-%d %d:%d:%d %c%2d%2d" - -enum { SETF_CREAT = 1, SETF_EXCLD = 2 }; -static struct field * -setfield(struct card *card, char *key, char *val, int opt); -static char *getfield(struct card *card, char *key); -static int gettime(struct card *card, char *key, time_t *tp); -static int settime(struct card *card, char *key, time_t t); -static int validkey(char *key); -static int validfield(struct field *field); -static int validcard(struct card *card); -static struct field *revfield(struct field **f); - -int readcard(FILE *fp, struct card *card, int *nline, int maxnl) -{ -#define INCNLINE(n) do { \ - if (maxnl - (*nline) < (n)) { \ - apperr = AENLINE; \ - goto ERR; \ - } \ - (*nline) += (n); \ -} while (0) - -#define CHK_VALSZ(n) do { \ - if (val - vbuf >= VALSZ - (n)) { \ - apperr = AEVALSZ; \ - goto ERR; \ - } \ -} while (0) - -#define SAVE_FIELD() do { \ - struct field *f; \ -\ - if (!(f = setfield(card, kbuf, vbuf, SETF_CREAT|SETF_EXCLD)) \ - || validfield(f)) { \ - *nline = keylno; \ - goto ERR; \ - } \ -} while (0) - - char line[LINESZ], kbuf[KEYSZ], vbuf[VALSZ], *val; - size_t sep, n; - int ch, keylno, nf; - - if (feof(fp)) - return 0; - memset(card, 0, sizeof *card); - *nline = 0; - nf = 0; - while ((ch = fgetc(fp)) == '\n') { - INCNLINE(1); - card->leadnewl++; - } - if (ungetc(ch, fp) != ch) { - apperr = AESYS; - goto ERR; - } - val = NULL; - while (fgets(line, LINESZ, fp)) { - INCNLINE(1); - n = strlen(line); - if (line[n - 1] != '\n' && !feof(fp)) { - apperr = AELINESZ; - goto ERR; - } - if (line[0] == '%') { - /* end of card */ - if (!(card->sep = strdup(line))) { - apperr = AESYS; - goto ERR; - } - break; - } else if (line[0] == '\t' || line[0] == '\n') { - /* successive value line */ - if (!val) { - apperr = AENOKEY; - goto ERR; - } - CHK_VALSZ(n); - val = stpcpy(val, line); - } else { - /* new field */ - if (nf >= NFIELD) { - apperr = AENFIELD; - goto ERR; - } - if (val) - SAVE_FIELD(); - keylno = *nline; - nf++; - val = vbuf; - *val = '\0'; - sep = strcspn(line, "\n\t"); - if (sep >= KEYSZ) { - apperr = AEKEYSZ; - goto ERR; - } - *stpncpy(kbuf, line, sep) = '\0'; - if (validkey(kbuf)) - goto ERR; - if (!line[sep]) - continue; - CHK_VALSZ(n - sep); - val = stpcpy(val, &line[sep]); - } - } - if (ferror(fp)) { - apperr = AESYS; - goto ERR; - } - if (val) { - SAVE_FIELD(); - if (validcard(card)) { - *nline = card->leadnewl + 1; - goto ERR; - } - } - revfield(&card->field); - return 1; -ERR: destrcard(card); - return -1; - -#undef INCNLINE -#undef CHK_VALSZ -#undef SAVE_FIELD -} - -int writecard(FILE *fp, struct card *card) -{ - struct field *f; - int i; - - for (i = 0; i < card->leadnewl; i++) - if (fputc('\n', fp) == EOF) { - apperr = AESYS; - return -1; - } - for (f = card->field; f != NULL; f = f->next) { - if (fprintf(fp, "%s%s", f->key, f->val) < 0) { - apperr = AESYS; - return -1; - } - } - return 0; -} - -void destrcard(struct card *card) -{ - struct field *i, *j; - - free(card->sep); - for (i = card->field; i != NULL; i = j) { - free(i->key); - free(i->val); - j = i->next; - free(i); - } -} - -char *getmod(struct card *card) -{ - return getfield(card, MOD); -} - -char *getques(struct card *card) -{ - return getfield(card, QUES); -} - -char *getansw(struct card *card) -{ - return getfield(card, ANSW); -} - -time_t getprev(struct card *card) -{ - time_t t; - - gettime(card, PREV, &t); - return t; -} - -time_t getnext(struct card *card) -{ - time_t t; - - gettime(card, NEXT, &t); - return t; -} - -int setprev(struct card *card, time_t prev) -{ - return settime(card, PREV, prev); -} - -int setnext(struct card *card, time_t next) -{ - return settime(card, NEXT, next); -} - -int parsetm(char *s, time_t *clock) -{ - int year, mon, day, hour, min, sec, zhour, zmin; - char zsign; - struct tm dtime; - - memset(&dtime, 0, sizeof dtime); - while (*s && isspace(*s)) - s++; - if (sscanf(s, TIMEFMT_P, &year, &mon, &day, &hour, &min, &sec, - &zsign, &zhour, &zmin) != 9 - || zsign != '-' && zsign != '+') { - apperr = AETIMEF; - return -1; - } - dtime.tm_year = year - 1900; - dtime.tm_mon = mon - 1; - dtime.tm_mday = day; - dtime.tm_hour = hour; - dtime.tm_min = min; - dtime.tm_sec = sec; - *clock = timegm(&dtime); - if (zsign == '+') - *clock -= zmin*60 + zhour*3600; - else - *clock += zmin*60 + zhour*3600; - return 0; -} - -char *normval(char *s, char *buf, int n) -{ - char *sp, *bp; - - while (*s == '\n') - s++; - for (sp = s, bp = buf; bp != &buf[n] && *sp; sp++) - if (*sp != '\t' || sp > s && sp[-1] != '\n') - *bp++ = *sp; - if (bp == &buf[n]) - return NULL; - while (bp > buf && bp[-1] == '\n') - bp--; - *bp = '\0'; - return buf; -} - -static struct field * -setfield(struct card *card, char *key, char *val, int opt) -{ - struct field *i; - - if (strlen(key) >= KEYSZ) { - apperr = AEKEYSZ; - return NULL; - } - for (i = card->field; i != NULL; i = i->next) - if (!strcmp(i->key, key)) { - if (opt & SETF_EXCLD) { - apperr = AEDUPKEY; - return NULL; - } - break; - } - if (!i) { - if (!(opt & SETF_CREAT)) { - apperr = AEINVKEY; - return NULL; - } - if (!(i = malloc(sizeof *i))) { - apperr = AESYS; - return NULL; - } - memset(i, 0, sizeof *i); - if (!(i->key = strdup(key))) { - free(i); - apperr = AESYS; - return NULL; - } - i->next = card->field; - card->field = i; - } - free(i->val); - if (!(i->val = strdup(val))) { - apperr = AESYS; - return NULL; - } - return i; -} - -static int validkey(char *key) -{ - while (*key && (isalpha(*key) || isdigit(*key) || *key == '_')) - key++; - if (*key) - apperr = AEINVKEY; - return *key; -} - -static int validfield(struct field *field) -{ - time_t t; - - if (!strcmp(field->key, PREV) || !strcmp(field->key, NEXT)) - return parsetm(field->val, &t); - return 0; -} - -static int validcard(struct card *card) -{ - if (!getques(card) || !getansw(card)) { - apperr = AEMFIELD; - return -1; - } - return 0; -} - -static char *getfield(struct card *card, char *key) -{ - struct field *i; - - for (i = card->field; i != NULL; i = i->next) - if (!strcmp(i->key, key)) - return i->val; - return NULL; -} - -static int gettime(struct card *card, char *key, time_t *tp) -{ - struct tm buf; - char *val; - - if (!(val = getfield(card, key))) { - *tp = 0; - return 0; - } - return parsetm(val, tp); -} - -static int settime(struct card *card, char *key, time_t t) -{ - struct tm *lctime; - char buf[VALSZ]; - size_t n; - - if (!(lctime = localtime(&t))) { apperr = AESYS; return -1; } - buf[0] = '\t'; - if (!(n = strftime(&buf[1], VALSZ - 2, TIMEFMT " %z", - lctime))) { - apperr = AEVALSZ; return -1; - } - buf[1 + n] = '\n'; - buf[2 + n] = '\0'; - return setfield(card, key, buf, SETF_CREAT) ? 0 : -1; -} - -static struct field *revfield(struct field **f) -{ - struct field *c, *n; - - c = *f; - if (c && (n = c->next)) { - revfield(&n)->next = c; - c->next = NULL; - *f = n; - } - return c; -} diff --git a/card.h b/card.h deleted file mode 100644 index 0cff566..0000000 --- a/card.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef CARD_H -#define CARD_H - -#include -#include "applim.h" - -struct field { - char *key; - char *val; - struct field *next; -}; - -struct card { - int leadnewl; - struct field *field; - char *sep; -}; - -int readcard(FILE *fp, struct card *card, int *nline, int maxnl); -int writecard(FILE *fp, struct card *card); -void destrcard(struct card *card); -char *getmod(struct card *card); -char *getques(struct card *card); -char *getansw(struct card *card); -time_t getprev(struct card *card); -time_t getnext(struct card *card); -int setprev(struct card *card, time_t prev); -int setnext(struct card *card, time_t next); -int parsetm(char *s, time_t *tp); -char *normval(char *s, char *buf, int n); - -#endif diff --git a/ctab.c b/ctab.c deleted file mode 100644 index 2f05414..0000000 --- a/ctab.c +++ /dev/null @@ -1,139 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "apperr.h" -#include "applim.h" -#include "card.h" -#include "ctab.h" - -static char *backup(char *src); - -char *bakfname; -int lineno, bakferr; - -int loadctab(char *filename, struct card *cards, int maxn) -{ - FILE *fp; - int ret, ncard, nline, maxnl, n; - - ret = -1; - if (!(fp = fopen(filename, "r"))) { - apperr = AESYS; - goto RET; - } - maxnl = NLINE; - ncard = lineno = 0; - while (ncard < maxn) { - n = readcard(fp, &cards[ncard], &nline, maxnl); - maxnl -= nline; - if (n == 0) - break; - if (n == -1) { - lineno = NLINE - maxnl; - goto ERR; - } - ncard++; - } - if (feof(fp)) { - lineno = 0; - ret = ncard; - } - else { - apperr = AENCARD; - goto ERR; - } -CLS: fclose(fp); -RET: return ret; -ERR: while (ncard-- > 0) - destrcard(&cards[ncard]); - goto CLS; -} - -int dumpctab(char *filename, struct card *cards, int n) -{ - sigset_t bset, oset; - FILE *fp; - int ret, i; - - ret = -1; - sigemptyset(&bset); - sigaddset(&bset, SIGHUP); - sigaddset(&bset, SIGINT); - sigaddset(&bset, SIGTERM); - sigaddset(&bset, SIGQUIT); - sigaddset(&bset, SIGTSTP); - sigprocmask(SIG_BLOCK, &bset, &oset); - if (!(bakfname = backup(filename))) { - bakferr = apperr; - apperr = AEBACKUP; - goto RET; - } - if (!(fp = fopen(filename, "w"))) { - apperr = AESYS; - goto WERR; - } - for (i = 0; i < n; i++) { - if (writecard(fp, &cards[i]) == -1) - goto WERR; - if (cards[i].sep && fputs(cards[i].sep, fp) == EOF) - goto WERR; - } - if (fclose(fp) == EOF) { - fp = NULL; - apperr = AESYS; - goto WERR; - } - unlink(bakfname); - bakfname = NULL; - ret = 0; -RET: sigprocmask(SIG_SETMASK, &oset, NULL); - return ret; -WERR: if (fp) - fclose(fp); - goto RET; -} - -char *getbname(char *src) -{ - static char bak[PATHSZ]; - int n; - - n = snprintf(bak, PATHSZ, "%s~", src); - if (n >= PATHSZ) { apperr = AEPATHSZ; return NULL; } - if (n < 0) { apperr = AESYS; return NULL; } - return bak; -} - -static char *backup(char *src) -{ - char *ret, *dst, buf[8192]; - int fd[2], n; - - ret = NULL; - dst = getbname(src); - if ((fd[1] = open(dst, O_WRONLY|O_CREAT|O_EXCL)) == -1) { - apperr = AESYS; - goto RET; - } - if ((fd[0] = open(src, O_RDONLY)) == -1) { - apperr = AESYS; - goto CL1; - } - while ((n = read(fd[0], buf, sizeof buf)) > 0) - if (write(fd[1], buf, n) != n) { - apperr = AESYS; - goto CL0; - } - if (n < 0) { - apperr = AESYS; - goto CL0; - } - ret = dst; -CL0: close(fd[0]); -CL1: close(fd[1]); -RET: return ret; -} diff --git a/ctab.h b/ctab.h deleted file mode 100644 index 8dcd136..0000000 --- a/ctab.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CTAB_H -#define CTAB_H - -#include "card.h" - -extern char *bakfname; -extern int lineno, bakferr; - -/* Load at most `maxn` cards from the file `filename` into `cards` - * - * Upon successful completion the number of loaded cards is returned. - * Otherwise, -1 is returned and the global variable `apperr` is set - * to indicate the error. - * - * If the error occurred during parsing, the global variable `lineno` - * is also set to the line number where the error occurred. - * Otherwise, `lineno` is set to 0. - */ -int loadctab(char *filename, struct card *cards, int maxn); - -/* Dump `n` cards in `cards` to the file `filename`. - * - * Upon successful completion 0 is returned. - * Otherwise, -1 is returned and the global variable `apperr` is set - * to indicate the error. - * - * If the error is failing to create the backup file, `apperr` is set - * to `AEBACKUP` and the global variable `bakferr` is set to indicate - * the detailed reason. - * - * If the error occurred but the backup file was created, the global - * variable `bakfname` is also set to the path of the backup file. - * Otherwise, `bakfname` is set to `NULL`. - */ -int dumpctab(char *filename, struct card *cards, int n); - -/* return the desirable backup file */ -char *getbname(char *src); - -#endif diff --git a/hardv.man1 b/hardv.1.s similarity index 91% rename from hardv.man1 rename to hardv.1.s index ef8ae34..8f95205 100644 --- a/hardv.man1 +++ b/hardv.1.s @@ -57,6 +57,16 @@ is earlier than or within today will be quizzed. .TP \fB-r Quiz cards within a file in a random order. +This option implies the \fB-d\fR option. + +.TP +\fB-d +Optimize for disk I/O instead of memory usage. +By default, \fBhardv\fR keeps only one card in the memory +by using more disk I/O. +With this option specified, +\fBhardv\fR uses less disk I/O, +but load all cards of a file into the memory. .TP \fB-n \fIn\fR @@ -133,14 +143,15 @@ A 2 You could modify the \fBPREV\fR and \fBNEXT\fR fields to manually tune the quiz time. -If the card doesn't contain the \fBPREV\fR field -or it's not later than \fB1970-01-01 00:00:00 +0000\fR, +If the card doesn't contain the \fBPREV\fR field, it will be reset to the start time of the current quiz. -If the card doesn't contain the \fBNEXT\fR field -or it's not later than \fB1970-01-01 00:00:00 +0000\fR, +If the card doesn't contain the \fBNEXT\fR field, it will be reset to the start time of the current quiz. +Both \fBPREV\fR and \fBNEXT\fR are required to be later than +\fB1970-01-01 00:00:00 +0000\fR. + A card is allowed to contain other fields not mentioned above, as long as the key consists of alphabets, digits and underlines. However, field keys consisting of only uppercase alphabets are reserved @@ -259,6 +270,7 @@ A line in input files must be less than \fBLINESZ\fR bytes. .TP \fBNCARD\fR The number of cards in an input file can't exceed \fBNCARD\fR. +This limitation is only enabled while the \fB-d\fR option is specified. .TP \fBNFIELD\fR diff --git a/hardv.c b/hardv.c new file mode 100644 index 0000000..5bc6038 --- /dev/null +++ b/hardv.c @@ -0,0 +1,858 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define KCHAR "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "0123456789_" +#define SHELL "/bin/sh" +#define DAY (60*60*24) +#define PATHSZ 32767 +#define NLINE 32767 +#define LINESZ 32767 +#define NCARD 32767 +#define NFIELD 16 +#define KEYSZ 8 +#define VALSZ 32767 +#define Q "Q" +#define A "A" +#define MOD "MOD" +#define PREV "PREV" +#define NEXT "NEXT" + +struct field { + char *key; + char *val; + struct field *next; +}; +struct card { + int leadnewl; + struct field *field; + char *sep; +}; +struct opt { + int exact; + int lowio; + int rand; + int maxn; +}; + +char *progname = "hardv"; +char *filename, *swapname; +int sigtab[] = {SIGHUP,SIGINT,SIGTERM,SIGQUIT,0}; +sigset_t bset, oset; /* block set, old set */ +jmp_buf jrmswp, jdump; +int aborted; +struct opt opt; +int card1 = 1; +int lineno; +time_t now; + +void init(int argc, char **argv); +void help(FILE *fp, int ret); +void pversion(FILE *fp, int ret); +void lowio(char *fn); +void lowmem(char *fn); +char *mkswap(char *fn); +void stdquiz(struct card *card); +void modquiz(struct card *card); +void sety(struct card *card); +void setn(struct card *card); +int loadcard(FILE *fp, struct card *card); +void dumpcard(FILE *fp, struct card *card); +void godump(int sig); +void parserr(char *s); +void dumperr(char *s); + +int main(int argc, char **argv) +{ + /* opt and optind is set by init() */ + init(argc, argv); + argv += optind; + argc -= optind; + while (*argv) { + if (opt.lowio) + lowio(*argv); /* optimize for disk I/O */ + else + lowmem(*argv); /* optimize for memory */ + argv++; + } + return 0; +} + +void setsig(void (*rtn)(int)); +void destrcard(struct card *card); +char *normval(char *s, char *buf, int n); +int isnow(struct card *card); +void shuf(struct card *a[], int n); +int isvalidf(struct field *f); +time_t tmparse(char *s); +struct field *revfield(struct field **f); +void fatal(char *s); +void syserr(jmp_buf *j); +time_t getdiff(struct card *card); +char *timev(time_t clock); +char *getv(struct card *card, char *k); +char *setv(struct card *card, char *k, char *v); +int pindent(char *s); + +void init(int argc, char *argv[]) +{ + int ch, *sig; + + srand(getpid()^time(NULL)); + if ((now = tmparse(getenv("HARDV_NOW"))) <= 0) + time(&now); + memset(&opt, 0, sizeof opt); + opt.maxn = -1; + while ((ch = getopt(argc, argv, "dhvern:")) != -1) + switch (ch) { + case 'e': + opt.exact = 1; + break; + case 'r': + opt.rand = 1; + case 'd': + opt.lowio = 1; + break; + case 'n': + if ((opt.maxn = atoi(optarg)) <= 0) + help(stderr, -1); + break; + case 'h': + help(stdout, 0); + case 'v': + pversion(stdout, 0); + case '?': + default: + help(stderr, -1); + } + if (optind >= argc) + help(stderr, -1); + sigemptyset(&bset); + for (sig=sigtab; *sig; sig++) + sigaddset(&bset, *sig); + sigaddset(&bset, SIGTSTP); +} + +void help(FILE *fp, int ret) +{ + fputs("usage:\n", fp); + fputs("\thardv [options] file ...\n", fp); + fputs("\thardv -h|-v\n", fp); + fputs("\n", fp); + fputs("options\n", fp); + fputs("\n", fp); + fputs("-e enable exact quiz time\n", fp); + fputs("-r randomize the quiz order within a file\n" + "this option implies -d\n" , fp); + fputs("-n quiz at most cards\n", fp); + fputs("-d optimize for disk i/o instead of memory\n", fp); + fputs("-h print this help information\n", fp); + fputs("-v print version and building arguments\n", fp); + exit(ret); +} + +void pversion(FILE *fp, int ret) +{ + fprintf(fp, "hardv %s\n", VERSION); + fprintf(fp, "%s\n", COPYRT); + fputc('\n', fp); + fprintf(fp, "NLINE: %d\n", NLINE); + fprintf(fp, "LINESZ: %d\n", LINESZ); + fprintf(fp, "NCARD: %d\n", NCARD); + fprintf(fp, "NFIELD: %d\n", NFIELD); + fprintf(fp, "KEYSZ: %d\n", KEYSZ); + fprintf(fp, "VALSZ: %d\n", VALSZ); + fprintf(fp, "PATHSZ: %d\n", PATHSZ); + exit(ret); +} + +void lowio(char *fn) +{ + struct card ctab[NCARD], *plan[NCARD], *card; + char lb[LINESZ]; + FILE *fp, *sp; + char *sn; + int nc, np, i; + + filename = fn; + swapname = sn = mkswap(fn); + if (setjmp(jrmswp)) { + unlink(sn); + exit(-1); + } + /* use r+ for sp to avoid recreation */ + if (!(fp=fopen(fn, "r")) || !(sp=fopen(sn, "r+"))) { + perror(progname); + longjmp(jrmswp, 1); + } + lineno = 0; + nc = 0; + while (nc < NCARD && loadcard(fp, ctab+nc)) + nc++; + if (!feof(fp)) { + fprintf(stderr, + "%s: too many cards in %s\n", progname, fn); + longjmp(jrmswp, 1); + } + fclose(fp); + np = 0; + for (card=ctab; cardfield && isnow(card)) + plan[np++] = card; + if (opt.rand) + shuf(plan, np); + /* if a signal in sigtab is delivered, jump to dump */ + if (setjmp(jdump)) + goto DUMP; + sigprocmask(SIG_BLOCK, &bset, &oset); + setsig(godump); + sigprocmask(SIG_SETMASK, &oset, NULL); + for (i=0; opt.maxn && i 0) + opt.maxn--; + } +DUMP: sigprocmask(SIG_BLOCK, &bset, &oset); + for (card=ctab; cardfield && isnow(cp)) { + if (getv(cp, MOD)) + modquiz(cp); + else + stdquiz(cp); + if (opt.maxn > 0) + opt.maxn--; + card1 = 0; + } + sigprocmask(SIG_BLOCK, &bset, &oset); + dumpcard(sp, &cb); + cp = NULL; + destrcard(&cb); + sigprocmask(SIG_SETMASK, &oset, NULL); + } +DUMP: sigprocmask(SIG_BLOCK, &bset, &oset); + if (cp) { + dumpcard(sp, cp); + destrcard(cp); + } + while (loadcard(fp, &cb)) { + dumpcard(sp, &cb); + destrcard(&cb); + } + if (fflush(sp) == EOF) + dumperr(strerror(errno)); + if (!(fp=freopen(fn, "w", fp))) + dumperr(strerror(errno)); + fseek(sp, 0, SEEK_SET); + while (fgets(lb, LINESZ, sp)) + if (fputs(lb, fp) == EOF) + dumperr(strerror(errno)); + fclose(fp); + fclose(sp); + unlink(sn); + if (aborted) + exit(127+aborted); + setsig(SIG_DFL); + sigprocmask(SIG_SETMASK, &oset, NULL); +} + +char *mkswap(char *fn) +{ + static char sn[PATHSZ]; + struct stat st; + int fd; + + if (snprintf(sn, PATHSZ, "%s~", fn) >= PATHSZ) + fatal("file path too long"); + if (stat(fn, &st) == -1) + syserr(NULL); + if ((fd = open(sn, O_CREAT|O_EXCL, st.st_mode)) == -1) { + if (errno == EEXIST) { + fprintf(stderr, + "%s: The swap file %s is detected.\n" + "This may be caused by " + "simultaneous quiz/editing, " + "or a previous crash.\n" + "If it's caused by a crash, " + "you should compare " + "the original file with the swap file, " + "to recover data\n", + progname, sn); + exit(-1); + } + syserr(NULL); + } + close(fd); + return sn; +} + +void stdquiz(struct card *card) +{ + char in[LINESZ], ques[VALSZ], answ[VALSZ], *act; + + if (!card1) + putchar('\n'); + normval(getv(card, Q), ques, VALSZ); + normval(getv(card, A), answ, VALSZ); + puts("Q:\n"); + pindent(ques); + puts("\n"); + fflush(stdout); +CHECK: + fputs("Press to check the answer.\n", stdout); + fflush(stdout); + while (!fgets(in, LINESZ, stdin)) { + if (feof(stdin)) + longjmp(jdump, 1); + if (errno != EINTR) + syserr(&jdump); + } + if (strcmp(in, "\n")) + goto CHECK; + puts("A:\n"); + pindent(answ); + puts("\n"); + fflush(stdout); +QUERY: + fputs("Do you recall? (y/n/s)\n", stdout); + fflush(stdout); + while (!fgets(in, LINESZ, stdin)) { + if (feof(stdin)) + longjmp(jdump, 1); + if (errno != EINTR) + syserr(&jdump); + } + act = strstr("y\nn\ns\n", in); + if (!act || *act == '\n') + goto QUERY; + switch (*act) { + case 'y': sety(card); break; + case 'n': setn(card); break; + } +} + +void modquiz(struct card *card) +{ + char pfx[] = "HARDV_F_"; + char sprev[64], snext[64], snow[64], first[2]; + char k[KEYSZ + sizeof(pfx) - 1]; + char mod[VALSZ], q[VALSZ], a[VALSZ]; + struct field *f; + pid_t pid; + int stat; + + if ((pid=fork()) == -1) + syserr(&jdump); + /* child */ + if (pid == 0) { + strcpy(k, pfx); + normval(getv(card, MOD), mod, VALSZ); + normval(getv(card, Q), q, VALSZ); + normval(getv(card, A), a, VALSZ); + sprintf(snext, "%ld", (long)tmparse(getv(card, NEXT))); + sprintf(sprev, "%ld", (long)tmparse(getv(card, PREV))); + sprintf(snow, "%ld", (long)now); + first[0] = first[1] = '\0'; + if (card1) + first[0] = '1'; + if ( setenv("HARDV_Q", q, 1) == -1 || + setenv("HARDV_A", a, 1) == -1 || + setenv("HARDV_NEXT", snext, 1) == -1 || + setenv("HARDV_PREV", sprev, 1) == -1 || + setenv("HARDV_NOW", snow, 1) == -1 || + setenv("HARDV_FIRST", first, 1) == -1 + ) + syserr(NULL); + for (f=card->field; f!=NULL; f=f->next) { + strcpy(&k[sizeof(pfx)-1], f->key); + if (setenv(k, f->val, 1) == -1) + syserr(NULL); + } + if (execl(SHELL, SHELL, "-c", mod, NULL) == -1) + syserr(NULL); + } + /* parent */ + while (waitpid(pid, &stat, 0) == -1) + if (errno != EINTR) + syserr(&jdump); + switch (WEXITSTATUS(stat)) { + case 0: sety(card); break; + case 1: setn(card); break; + } +} + +void sety(struct card *card) +{ + time_t diff; + + /* signal atomic */ + sigprocmask(SIG_BLOCK, &bset, &oset); + diff = getdiff(card); + if (!setv(card, PREV, timev(now))) + syserr(&jdump); + if (!setv(card, NEXT, timev(now + 2*diff))) + syserr(&jdump); + sigprocmask(SIG_SETMASK, &oset, NULL); +} + +void setn(struct card *card) +{ + /* signal atomic */ + sigprocmask(SIG_BLOCK, &bset, &oset); + if (!setv(card, PREV, timev(now))) + syserr(&jdump); + if (!setv(card, NEXT, timev(now+DAY))) + syserr(&jdump); + sigprocmask(SIG_SETMASK, &oset, NULL); +} + +/* return 1 for success, 0 for EOF */ +int loadcard(FILE *fp, struct card *card) +{ + static char *enl = "too many lines"; + static char *evf = "invalid value"; + static char *evsz = "value too large"; + static char lb[LINESZ], k[KEYSZ], v[VALSZ], *vp; + struct field *f; + size_t s, n; + int ch, nf, nq, na; + int kl; /* starting line of current field */ + int ol; /* lineno swap */ + + ol = lineno; + if (feof(fp)) + return 0; + memset(card, 0, sizeof *card); + nf = 0; + while ((ch=fgetc(fp)) == '\n') { + if (lineno >= NLINE) + parserr(enl); + lineno++; + card->leadnewl++; + } + if (ungetc(ch, fp) != ch) + syserr(&jrmswp); + vp = NULL; + f = NULL; + nq = na = 0; + while (fgets(lb, LINESZ, fp)) { + if (lineno >= NLINE) + parserr(enl); + lineno++; + n = strlen(lb); + if (lb[n - 1] != '\n' && !feof(fp)) + parserr("line too long"); + if (lb[0] == '%') { + /* end */ + if (!(card->sep = strdup(lb))) + syserr(&jrmswp); + break; + } else if (lb[0] == '\t' || lb[0] == '\n') { + /* successive lines in value */ + if (!f) + parserr("key is expected"); + if (vp-v >= VALSZ-n) + parserr(evsz); + vp = stpcpy(vp, lb); + } else { + /* new field */ + if (nf >= NFIELD) + parserr("too many fields"); + if (f) { + f->key = strdup(k); + f->val = strdup(v); + if (!f->key || !f->val) + syserr(&jrmswp); + if (!isvalidf(f)) { + lineno = kl; + parserr(evf); + } + } + kl = lineno; + nf++; + vp = v; + *vp = '\0'; + s = strcspn(lb, "\n\t"); + if (s >= KEYSZ) + parserr("field key too large"); + *stpncpy(k, lb, s) = '\0'; + if (k[strspn(k, KCHAR)]) + parserr("invalid key"); + for (f=card->field; f; f=f->next) + if (!strcmp(f->key, k)) + parserr("duplicated key"); + if (!strcmp(k, Q)) nq++; + if (!strcmp(k, A)) na++; + if (!(f = malloc(sizeof *f))) + syserr(&jrmswp); + f->next = card->field; + card->field = f; + if (!lb[s]) + continue; + if (vp-v >= VALSZ-(n-s)) + parserr(evsz); + vp = stpcpy(vp, &lb[s]); + } + } + if (ferror(fp)) + syserr(&jrmswp); + if (f) { + if (!(f->key = strdup(k)) || !(f->val = strdup(v))) + syserr(&jrmswp); + if (!isvalidf(f)) { + lineno = kl; + parserr(evf); + } + if (nq != 1 || na != 1) { + lineno = ol+card->leadnewl+1; + parserr("no mandatory field"); + } + /* fields were installed in the revered order */ + revfield(&card->field); + } + return 1; +} + +void dumpcard(FILE *fp, struct card *card) +{ + struct field *f; + int i; + + for (i = 0; i < card->leadnewl; i++) + if (fputc('\n', fp) == EOF) + dumperr(strerror(errno)); + for (f = card->field; f != NULL; f = f->next) + if (fprintf(fp, "%s%s", f->key, f->val) < 0) + dumperr(strerror(errno)); + if (card->sep && fputs(card->sep, fp) == EOF) + dumperr(strerror(errno)); +} + +void godump(int sig) +{ + aborted = sig; + longjmp(jdump, 1); +} + +void parserr(char *s) +{ + fprintf(stderr, "%s, line %d: %s\n", filename, lineno, s); + longjmp(jrmswp, 1); +} + +void dumperr(char *s) +{ + fprintf(stderr, "%s: %s\n", progname, s); + fprintf(stderr, + "The swap file %s is created, " + "You should compare the original file with it " + "to recover data\n", + swapname); + exit(-1); +} + +void setsig(void (*rtn)(int)) +{ + int *sig; + + for (sig=sigtab; *sig; sig++) + signal(*sig, rtn); +} + +void destrcard(struct card *card) +{ + struct field *i, *j; + + free(card->sep); + for (i = card->field; i != NULL; i = j) { + free(i->key); + free(i->val); + j = i->next; + free(i); + } +} + +char *normval(char *s, char *buf, int n) +{ + char *sp, *bp; + + while (*s == '\n') + s++; + for (sp = s, bp = buf; bp != &buf[n] && *sp; sp++) + if (*sp != '\t' || sp > s && sp[-1] != '\n') + *bp++ = *sp; + if (bp == &buf[n]) + return NULL; + while (bp > buf && bp[-1] == '\n') + bp--; + *bp = '\0'; + return buf; +} + +int isnow(struct card *card) +{ + struct tm today, theday; + time_t next; + + next = tmparse(getv(card, NEXT)); + if (next <= 0) + next = now; + if (opt.exact) + return now >= next; + memcpy(&today, localtime(&now), sizeof today); + memcpy(&theday, localtime(&next), sizeof theday); + if (today.tm_year > theday.tm_year) + return 1; + if (today.tm_year == theday.tm_year + && today.tm_mon > theday.tm_mon) + return 1; + if (today.tm_year == theday.tm_year + && today.tm_mon == theday.tm_mon + && today.tm_mday >= theday.tm_mday) + return 1; + return 0; +} + +void shuf(struct card *a[], int n) +{ + struct card *t; + int i, j; + + for (i=0; ikey, NEXT) || !strcmp(f->key, PREV)) + if (tmparse(f->val) <= 0) + return 0; + return 1; +} + +time_t tmparse(char *s) +{ + /* timegm() is not conforming to any standard, + * but provided by both BSD and Linux. + * Explicit declaration could avoid the trouble caused by + * feature test macros. + */ + extern time_t timegm(struct tm*); + unsigned hr, mi; + char sg; + struct tm tm; + time_t ck; + + if (!s) + return 0; + while (isspace(*s)) + s++; + memset(&tm, 0, sizeof tm); + if (!(s = strptime(s, "%Y-%m-%d %H:%M:%S", &tm)) + || (sscanf(s, " %[+-]%2u%2u", &sg, &hr, &mi) != 3)) + return 0; + ck = timegm(&tm); + if (sg == '+') + ck -= mi*60 + hr*3600L; + else + ck += mi*60 + hr*3600L; + return ck; +} + +/* in-place reverse the field list + * update *f to the reverse first element + * return the reversed last element + */ +struct field *revfield(struct field **f) +{ + struct field *c, *n; + + c = *f; + if (c && (n = c->next)) { + revfield(&n)->next = c; + c->next = NULL; + *f = n; + } + return c; +} + +void fatal(char *s) +{ + fprintf(stderr, "%s: %s\n", progname, s); + exit(-1); +} + +void syserr(jmp_buf *j) +{ + perror(progname); + if (j) + longjmp(*j, 1); + exit(-1); +} + +time_t getdiff(struct card *card) +{ + time_t prev, next, diff; + + prev = tmparse(getv(card, PREV)); + next = tmparse(getv(card, NEXT)); + if (prev <= 0) prev = now; + if (next <= 0) next = now; + if (next < prev || (diff = next - prev) < DAY) + diff = DAY; + return diff; +} + +char *timev(time_t clock) +{ + static char buf[VALSZ]; + struct tm *lc; + size_t n; + + lc = localtime(&clock); + buf[0] = '\t'; + n = strftime(&buf[1], VALSZ - 2, "%Y-%m-%d %H:%M:%S %z", lc); + buf[1 + n] = '\n'; + buf[2 + n] = '\0'; + return buf; +} + +char *getv(struct card *card, char *k) +{ + struct field *i; + + for (i=card->field; i; i=i->next) + if (!strcmp(i->key, k)) + return i->val; + return NULL; +} + +char *setv(struct card *card, char *k, char *v) +{ + struct field *i; + char *vb; + int newf; + + newf = 0; + for (i=card->field; i; i=i->next) + if (!strcmp(i->key, k)) + break; + if (!i) { + newf = 1; + if (!(i=malloc(sizeof *i))) + return NULL; + memset(i, 0, sizeof *i); + if (!(i->key=strdup(k))) { + free(i); + return NULL; + } + /* we must delay the linkage, + * since if we do this now and strdup(v) fails, + * the list will be corrupted. + * thus the caller can't dump cards before exit. */ + } + if (!(vb=strdup(v))) { + if (newf) { + free(i->key); + free(i); + } + return NULL; + } + free(i->val); + i->val = vb; + if (newf) { + /* delay linkage */ + i->next = card->field; + card->field = i; + } + return i->val; +} + +int pindent(char *s) +{ + char *sp; + + for (sp = s; *sp ; sp++) { + if (sp == s || sp[-1] == '\n') + putchar('\t'); + putchar(*sp); + } + return sp - s; +} diff --git a/learn.c b/learn.c deleted file mode 100644 index 2527cf5..0000000 --- a/learn.c +++ /dev/null @@ -1,271 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "card.h" -#include "ctab.h" -#include "apperr.h" -#include "applim.h" -#include "learn.h" -#define SHELL "/bin/sh" -#define DAY 60*60*24 - -#define CAVEAT \ - "* DO NOT EDIT INPUT FILES OR RUN ANOTHER INSTANCE OF HARDV " \ - "ON ANY OF THE SAME\n" \ - "* FILES BEFORE THIS INSTANCE EXITS.\n" - -static struct learnopt *learnopt; -static char *curfile; -static struct card cardtab[NCARD]; -static int ncard; - -static int isnow(struct card *card, time_t now); -static int exemod(struct card *card, time_t now); -static int recall(struct card *card, time_t now); -static int sety(struct card *card, time_t now); -static int setn(struct card *card, time_t now); -static void preset(struct card *card, time_t now, - time_t *prev, time_t *next, time_t *diff); -int pindent(char *s); - -int learn(char *filename, int now, struct learnopt *opt) -{ - static int plan[NCARD]; - struct card *card; - int i, j, swp, ret, stop, np; - - ret = -1; - curfile = filename; - learnopt = opt; - if ((ncard = loadctab(curfile, cardtab, NCARD)) == -1) - goto RET; - np = 0; - for (i = 0; i < ncard; i++) { - card = &cardtab[i]; - if (card->field && isnow(card, now)) - plan[np++] = i; - } - if (opt->rand) - for (i = 0; i < np; i++) { - j = i + rand() % (np - i); - swp = plan[i]; - plan[i] = plan[j]; - plan[j] = swp; - } - stop = 0; - for (i = 0; !stop && i < np && opt->maxn; i++) { - card = &cardtab[plan[i]]; - if (!opt->any) - puts(CAVEAT); - if (getmod(card)) { - if (exemod(card, now) == -1) - goto CLR; - } else switch(recall(card, now)) { - case -1: goto CLR; - case 1: stop = 1; - } - opt->any = 1; - if (opt->maxn > 0) - opt->maxn--; - } - ret = 0; -CLR: for (i = 0; i < ncard; i++) - destrcard(&cardtab[i]); -RET: return ret; -} - -static int isnow(struct card *card, time_t now) -{ - struct tm today, theday; - time_t next; - - next = getnext(card); - if (next <= 0) - next = now; - if (learnopt->exact) - return now >= next; - memcpy(&today, localtime(&now), sizeof today); - memcpy(&theday, localtime(&next), sizeof theday); - if (today.tm_year > theday.tm_year) - return 1; - if (today.tm_year == theday.tm_year - && today.tm_mon > theday.tm_mon) - return 1; - if (today.tm_year == theday.tm_year - && today.tm_mon == theday.tm_mon - && today.tm_mday >= theday.tm_mday) - return 1; - return 0; -} - -static int exemod(struct card *card, time_t now) -{ - const char fpref[] = "HARDV_F_"; - char sprev[64], snext[64], snow[64], first[2]; - char kbuf[KEYSZ + sizeof(fpref) - 1]; - char mod[VALSZ], q[VALSZ], a[VALSZ]; - struct field *f; - pid_t pid; - int stat; - - if ((pid = fork()) == -1) { - apperr = AESYS; - return -1; - } - /* child */ - if (pid == 0) { - strcpy(kbuf, fpref); - normval(getmod(card), mod, VALSZ); - normval(getques(card), q, VALSZ); - normval(getansw(card), a, VALSZ); - sprintf(snext, "%ld", (long)getnext(card)); - sprintf(sprev, "%ld", (long)getprev(card)); - sprintf(snow, "%ld", (long)now); - first[0] = first[1] = '\0'; - if (!learnopt->any) - first[0] = '1'; - if ( setenv("HARDV_Q", q, 1) == -1 || - setenv("HARDV_A", a, 1) == -1 || - setenv("HARDV_NEXT", snext, 1) == -1 || - setenv("HARDV_PREV", sprev, 1) == -1 || - setenv("HARDV_NOW", snow, 1) == -1 || - setenv("HARDV_FIRST", first, 1) == -1 - ) { - perror("setenv[1]"); - exit(2); - } - for (f = card->field; f != NULL; f = f->next) { - strcpy(&kbuf[sizeof(fpref) - 1], f->key); - if (setenv(kbuf, f->val, 1) == -1) { - perror("setenv[2]"); - exit(2); - } - } - if (execl(SHELL, SHELL, "-c", mod, NULL) == -1) { - apperr = AESYS; - exit(2); - } - } - /* parent */ - while (waitpid(pid, &stat, 0) == -1) - if (errno != EINTR) { - apperr = AESYS; - return -1; - } - switch (WEXITSTATUS(stat)) { - case 0: - if (sety(card, now) == -1) - return -1; - break; - case 1: - if (setn(card, now) == -1) - return -1; - break; - } - return 0; -} - -static int recall(struct card *card, time_t now) -{ -#define GETIN() do { \ - while (!fgets(in, sizeof in, stdin)) { \ - if (feof(stdin)) \ - return 1; \ - if (errno != EINTR) { \ - apperr = AESYS; \ - return -1; \ - } \ - } \ -} while (0) - - char in[BUFSIZ], ques[VALSZ], answ[VALSZ]; - - if (learnopt->any) - putchar('\n'); - normval(getques(card), ques, VALSZ); - normval(getansw(card), answ, VALSZ); - puts("Q:\n"); - pindent(ques); - puts("\n"); - fflush(stdout); -CHECK: - fputs("Press to check the answer.\n", stdout); - fflush(stdout); - GETIN(); - if (strcmp(in, "\n")) - goto CHECK; - puts("A:\n"); - pindent(answ); - puts("\n"); - fflush(stdout); -QUERY: - fputs("Do you recall? (y/n/s)\n", stdout); - fflush(stdout); - GETIN(); - if (strcmp(in, "y\n") && strcmp(in, "n\n") && strcmp(in, "s\n")) - goto QUERY; - switch (in[0]) { - case 'y': - if (sety(card, now) == -1) - return -1; - break; - case 'n': - if (setn(card, now) == -1) - return -1; - break; - } - return 0; -#undef GETIN -} - -static int sety(struct card *card, time_t now) -{ - time_t diff, prev, next; - - preset(card, now, &prev, &next, &diff); - if (setprev(card, now)) return -1; - if (setnext(card, now + 2*diff)) return -1; - if (dumpctab(curfile, cardtab, ncard)) return -1; - return 0; -} - -static int setn(struct card *card, time_t now) -{ - time_t diff, prev, next; - - preset(card, now, &prev, &next, &diff); - if (setprev(card, now)) return -1; - if (setnext(card, now + DAY)) return -1; - if (dumpctab(curfile, cardtab, ncard)) return -1; - return 0; -} - -static void preset(struct card *card, time_t now, - time_t *prev, time_t *next, time_t *diff) -{ - *prev = getprev(card); - if (*prev <= 0) - *prev = now; - *next = getnext(card); - if (*next <= 0) - *next = now; - if (*next < *prev || (*diff = *next - *prev) < DAY) - *diff = DAY; -} - -int pindent(char *s) -{ - char *sp; - - for (sp = s; *sp ; sp++) { - if (sp == s || sp[-1] == '\n') - putchar('\t'); - putchar(*sp); - } - return sp - s; -} diff --git a/learn.h b/learn.h deleted file mode 100644 index 8e6339d..0000000 --- a/learn.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef LEARN_H -#define LEARN_H - -#include "ctab.h" -#include "card.h" - -struct learnopt { - int any; - int exact; - int rand; - int maxn; -}; - -/* Load cards in `filename` with the quiz time `now` and options `opt`. - * - * Upon successful completion 0 is returned. - * Otherwise, -1 is returned and the global variable `apperr` is set - * to indicate the error. - * - * If the error occurred during parsing, the global variable `lineno` - * is also set to the line number where the error occurred. - * Otherwise, `lineno` is set to 0. - * - * If the error is failing to create the backup file, `apperr` is set - * to `AEBACKUP` and the global variable `bakferr` is set to indicate - * the detailed reason. - * - * If the error occurred during dumping but the backup file was created, - * the global variable `bakfname` is also set to the path of the - * backup file. Otherwise, `bakfname` is set to `NULL`. - */ -int learn(char *filename, int now, struct learnopt *opt); - -#endif diff --git a/main.c b/main.c deleted file mode 100644 index 4ac5b26..0000000 --- a/main.c +++ /dev/null @@ -1,145 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "apperr.h" -#include "applim.h" -#include "learn.h" -#include "ctab.h" - -static void help(FILE *fp); -static void pversion(FILE *fp); -static void report(char *fname); -static int safechk(char *fname); - -int main(int argc, char **argv) -{ - struct learnopt opt; - char *envnow; - time_t now; - int ch; - - srand(time(NULL)); - if ((envnow = getenv("HARDV_NOW"))) { - if (parsetm(envnow, &now) == -1) { - aeprint(envnow); - return 1; - } - } else - now = time(NULL); - memset(&opt, 0, sizeof opt); - opt.maxn = -1; - while ((ch = getopt(argc, argv, "hvern:")) != -1) - switch (ch) { - case 'e': - opt.exact = 1; - break; - case 'r': - opt.rand = 1; - break; - case 'n': - if ((opt.maxn = atoi(optarg)) <= 0) { - help(stderr); - return 1; - } - break; - case 'h': - help(stdout); - return 0; - case 'v': - pversion(stdout); - return 0; - case '?': - default: - help(stderr); - return 1; - } - argv += optind; - argc -= optind; - if (argc <= 0) { - help(stderr); - return 1; - } - while (*argv) { - if (safechk(*argv) == -1 || - learn(*argv, now, &opt) == -1) { - report(*argv); - return 1; - } - argv++; - } - return 0; -} - -static void help(FILE *fp) -{ - fputs("usage:\n", fp); - fputs("\thardv [options] file ...\n", fp); - fputs("\thardv -h|-v\n", fp); - fputs("\n", fp); - fputs("options\n", fp); - fputs("\n", fp); - fputs("-e enable exact quiz time\n", fp); - fputs("-r randomize the quiz order within a file\n", fp); - fputs("-n quiz at most cards\n", fp); - fputs("-h print this help information\n", fp); - fputs("-v print version and building arguments\n", fp); -} - -static void pversion(FILE *fp) -{ - fprintf(fp, "hardv %s\n", VERSION); - fprintf(fp, "%s\n", COPYRT); - fputc('\n', fp); - fprintf(fp, "NLINE: %d\n", NLINE); - fprintf(fp, "LINESZ: %d\n", LINESZ); - fprintf(fp, "NCARD: %d\n", NCARD); - fprintf(fp, "NFIELD: %d\n", NFIELD); - fprintf(fp, "KEYSZ: %d\n", KEYSZ); - fprintf(fp, "VALSZ: %d\n", VALSZ); - fprintf(fp, "PATHSZ: %d\n", PATHSZ); -} - -static void report(char *fname) -{ - if (apperr == AEEXBF) { - fprintf(stderr, "%s: %s\n", - aestr(apperr), getbname(fname)); - fputs("\nThis may be caused by a previous crash " - "or simultaneous quiz/editing.\n" - "You should check the backup file " - "and recover the data.\n", stderr); - return; - } - if (apperr == AEBACKUP) { - aeprint(fname); - fprintf(stderr, "%s: %s\n", getbname(fname), - aestr(bakferr)); - return; - } - if (lineno > 0) - fprintf(stderr, "%s, line %d: %s\n", fname, lineno, - aestr(apperr)); - else - aeprint(fname); - if (bakfname) - fprintf(stderr,"\nAn error occurred while saving %s. " - "A backup file is created at %s, you should " - "check it and recover the data.\n", fname, - bakfname); -} - -static int safechk(char *fname) -{ - char *bak; - - bak = getbname(fname); - if (access(bak, F_OK) == 0) { - apperr = AEEXBF; - return -1; - } - return 0; -} diff --git a/test/err/einvkey.ans b/test/err/einvkey.ans index 711f182..80d028c 100644 --- a/test/err/einvkey.ans +++ b/test/err/einvkey.ans @@ -1 +1 @@ -einvkey.tin.tmp, line 4: invalid field key +einvkey.tin.tmp, line 4: invalid key diff --git a/test/err/elinesz.ans b/test/err/elinesz.ans index 4e35ca9..4963231 100644 --- a/test/err/elinesz.ans +++ b/test/err/elinesz.ans @@ -1 +1 @@ -elinesz.tin.tmp, line 4: line too large +elinesz.tin.tmp, line 4: line too long diff --git a/test/err/enokey.ans b/test/err/enokey.ans index 51394a8..fd18533 100644 --- a/test/err/enokey.ans +++ b/test/err/enokey.ans @@ -1 +1 @@ -enokey.tin.tmp, line 1: filed key is expected +enokey.tin.tmp, line 1: key is expected diff --git a/test/err/etimef.ans b/test/err/etimef.ans index 38aad54..37a14bf 100644 --- a/test/err/etimef.ans +++ b/test/err/etimef.ans @@ -1 +1 @@ -etimef.tin.tmp, line 12: invalid time format +etimef.tin.tmp, line 12: invalid value diff --git a/test/err/etimef2.ans b/test/err/etimef2.ans index 1ebec20..a72fb51 100644 --- a/test/err/etimef2.ans +++ b/test/err/etimef2.ans @@ -1 +1 @@ -etimef2.tin.tmp, line 5: invalid time format +etimef2.tin.tmp, line 5: invalid value diff --git a/test/err/etimef3.ans b/test/err/etimef3.ans index df040ab..eba841c 100644 --- a/test/err/etimef3.ans +++ b/test/err/etimef3.ans @@ -1 +1 @@ -etimef3.tin.tmp, line 12: invalid time format +etimef3.tin.tmp, line 12: invalid value diff --git a/test/err/etimef4.ans b/test/err/etimef4.ans index f4c62c7..023e6fa 100644 --- a/test/err/etimef4.ans +++ b/test/err/etimef4.ans @@ -1 +1 @@ -etimef4.tin.tmp, line 12: invalid time format +etimef4.tin.tmp, line 12: invalid value diff --git a/test/err/etimef5.ans b/test/err/etimef5.ans new file mode 100644 index 0000000..7d24d19 --- /dev/null +++ b/test/err/etimef5.ans @@ -0,0 +1 @@ +etimef5.tin.tmp, line 12: invalid value diff --git a/test/err/etimef5.in b/test/err/etimef5.in new file mode 100644 index 0000000..8c801a4 --- /dev/null +++ b/test/err/etimef5.in @@ -0,0 +1,13 @@ +Q A +A B +% +Q A +A B +% +Q A +A B +% +Q A +A B +PREV 1970-01-01 00:00:00 +0000 +note yes diff --git a/test/err/evalsz.ans b/test/err/evalsz.ans index 601bfa7..28c6863 100644 --- a/test/err/evalsz.ans +++ b/test/err/evalsz.ans @@ -1 +1 @@ -evalsz.tin.tmp, line 500: feild value too large +evalsz.tin.tmp, line 500: value too large diff --git a/test/err/mfield1.ans b/test/err/mfield1.ans index 1d762b2..513eda2 100644 --- a/test/err/mfield1.ans +++ b/test/err/mfield1.ans @@ -1 +1 @@ -mfield1.tin.tmp, line 10: mandaroty field not found +mfield1.tin.tmp, line 10: no mandatory field diff --git a/test/err/mfield2.ans b/test/err/mfield2.ans index 6e28b7e..8c89293 100644 --- a/test/err/mfield2.ans +++ b/test/err/mfield2.ans @@ -1 +1 @@ -mfield2.tin.tmp, line 1: mandaroty field not found +mfield2.tin.tmp, line 1: no mandatory field diff --git a/test/err/mfield3.ans b/test/err/mfield3.ans index cac862f..98457c5 100644 --- a/test/err/mfield3.ans +++ b/test/err/mfield3.ans @@ -1 +1 @@ -mfield3.tin.tmp, line 14: mandaroty field not found +mfield3.tin.tmp, line 14: no mandatory field diff --git a/test/learn/4.act b/test/learn/4.act deleted file mode 100644 index 34c278d..0000000 --- a/test/learn/4.act +++ /dev/null @@ -1,10 +0,0 @@ - -y - -y - -y - -y - -n diff --git a/test/learn/4.ans b/test/learn/4.ans deleted file mode 100644 index 0db1d53..0000000 --- a/test/learn/4.ans +++ /dev/null @@ -1,24 +0,0 @@ -NEXT 2128-05-01 09:01:28 +0800 -Q X -A Y -PREV 2022-10-11 16:20:30 +0800 -% -NEXT 2128-05-01 09:01:28 +0800 -Q X -A Y -PREV 2022-10-11 16:20:30 +0800 -% -NEXT 2022-10-13 16:20:30 +0800 -Q X -A Y -PREV 2022-10-11 16:20:30 +0800 -%% -NEXT 2022-10-13 16:20:30 +0800 -Q X -A Y -PREV 2022-10-11 16:20:30 +0800 -% -NEXT 2022-10-12 16:20:30 +0800 -Q X -A Y -PREV 2022-10-11 16:20:30 +0800 diff --git a/test/learn/4.arg b/test/learn/4.arg deleted file mode 100644 index e69de29..0000000 diff --git a/test/learn/4.in b/test/learn/4.in deleted file mode 100644 index 4dad623..0000000 --- a/test/learn/4.in +++ /dev/null @@ -1,19 +0,0 @@ -Q X -A Y -PREV 1970-01-01 00:00:01 +0000 -% -Q X -A Y -PREV 1970-01-01 00:00:01 -0000 -% -Q X -A Y -PREV 1970-01-01 00:00:00 -0000 -%% -Q X -A Y -PREV 1970-01-01 00:00:00 +0000 -% -Q X -A Y -PREV 1969-12-01 08:00:00 +0000 diff --git a/test/learn/4.now b/test/learn/4.now deleted file mode 100644 index 3083788..0000000 --- a/test/learn/4.now +++ /dev/null @@ -1 +0,0 @@ -2022-10-11 16:20:30 +0800 diff --git a/version b/version index 197c4d5..4a36342 100644 --- a/version +++ b/version @@ -1 +1 @@ -2.4.0 +3.0.0