/* See LICENSE file for copyright and license details. */
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
#include "date.h"
#include "strlcpy.h"
#define BUF_MAX_SIZE 64
#define DATE_SIZE 11 /* 'YYYY-MM-DD\0' */
/* Execution modes */
enum {
DEF_MODE,
CHECKIN_MODE,
CREATE_MODE
};
/* Output formats */
enum {
LONG_OUT,
SHORT_OUT
};
/* Auxiliary procedures */
static void die(const char *fmt, ...);
static void usage(void);
static void read_file(char *date, int *num, const char *path);
static void write_file(const char *path, const char *date, int num);
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
va_end(ap);
exit(1);
}
static void
usage(void)
{
die("usage: schain [-krsv] [-cK num] [-w date] [file]");
}
static void
read_file(char *date, int *num, const char *path)
{
FILE *fp;
char *ptr;
char buf[BUF_MAX_SIZE];
if ((fp = fopen(path, "r")) == NULL)
die("%s: %s.", path, strerror(errno));
fgets(buf, BUF_MAX_SIZE, fp);
fclose(fp);
ptr = strtok(buf, "\t");
if (ptr == NULL)
goto parsedie;
if (date != NULL)
strlcpy(date, ptr, DATE_SIZE);
ptr = strtok(NULL, "\n");
if (ptr == NULL)
goto parsedie;
if (sscanf(ptr, "%d", num) == 0 || *num < 0)
goto parsedie;
return;
parsedie:
die("%s: Not an schain file.", path);
}
static void
write_file(const char *path, const char *date, int num)
{
FILE *fp;
if ((fp = fopen(path, "w")) == NULL)
die("%s: %s.", path, strerror(errno));
fprintf(fp, "%s\t%d\n", date, num);
fclose(fp);
}
int
main(int argc, char *argv[])
{
int opt, mode, out_fmt, num, check_diff;
const char *filepath;
char *datearg;
char date[DATE_SIZE];
num = 0;
datearg = NULL;
check_diff = 1;
mode = DEF_MODE;
out_fmt = LONG_OUT;
while ((opt = getopt(argc, argv, ":krsvc:K:w:")) != -1) {
switch (opt) {
case 'k':
mode = CHECKIN_MODE;
break;
case 'r':
mode = CREATE_MODE;
num = 0;
break;
case 's':
out_fmt = SHORT_OUT;
break;
case 'v':
die("schain %s", VERSION);
break; /* UNREACHABLE */
case 'c':
mode = CREATE_MODE;
sscanf(optarg, "%d", &num);
break;
case 'K':
mode = CHECKIN_MODE;
sscanf(optarg, "%d", &check_diff);
break;
case 'w':
datearg = optarg;
break;
default:
usage();
break; /* UNREACHABLE */
}
}
if (optind < argc) {
filepath = argv[optind];
} else {
if ((filepath = getenv("SCHAIN_DEF_FILE")) == NULL)
die("SCHAIN_DEF_FILE environment variable not set.");
}
if (num < 0 || check_diff < 0)
die("Error: not a positive number.");
if (datearg != NULL) {
if (strncmp(datearg, "y", DATE_SIZE) == 0)
strlcpy(date, date_str(time(NULL) - 86400), DATE_SIZE);
else
strlcpy(date, datearg, DATE_SIZE);
} else {
strlcpy(date, date_str(time(NULL)), DATE_SIZE);
}
if (is_date(date) < 0)
die("Error: not a valid YYYY-MM-DD date.");
switch (mode) {
case CREATE_MODE:
write_file(filepath, date, num);
break;
case CHECKIN_MODE:
read_file(NULL, &num, filepath);
if (num + check_diff < 0)
die("Check-in error: count in file too big. Abort.");
num += check_diff;
write_file(filepath, date, num);
break;
default:
read_file(date, &num, filepath);
break;
}
if (out_fmt == SHORT_OUT)
printf("%d\n", num);
else
printf("%s: %d %s.\n", date, num, unit);
return 0;
}