sw

simple wallet
git clone git://git.bitsmanent.org/sw
Log | Files | Refs | README | LICENSE

sw.c (8162B)


      1 /* See LICENSE file for copyright and license details.
      2  * sw is a simple wallet management tool for the terminal. */
      3 
      4 #define _XOPEN_SOURCE
      5 #define _GNU_SOURCE
      6 #include <stdarg.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 #include <time.h>
     11 #include <limits.h>
     12 
     13 #include "arg.h"
     14 char *argv0;
     15 
     16 enum {F_DATEFROM, F_DATETO, F_TEXT, F_NOTEXT};
     17 
     18 typedef union {
     19 	int i;
     20 	unsigned int ui;
     21 	float f;
     22 	const void *v;
     23 } Arg;
     24 
     25 typedef struct Movement Movement;
     26 struct Movement {
     27 	int id;
     28 	int ts;
     29 	int filtered;
     30 	float amount;
     31 	char note[64];
     32 	Movement *next;
     33 };
     34 
     35 typedef struct {
     36 	float amount, pamount;
     37 	float income, pincome;
     38 	float expense, pexpense;
     39 	int count, pcount;
     40 } Totals;
     41 
     42 typedef struct Filter Filter;
     43 struct Filter {
     44 	int type;
     45 	Arg arg;
     46 	Filter *next;
     47 };
     48 
     49 /* function declarations */
     50 int addmov(char *date, float amount, char *note);
     51 void addfilter(unsigned int type, void *data);
     52 void attach(Movement *m);
     53 void attachfltr(Filter *f);
     54 void attach_sorted_desc(Movement **head, Movement *m);
     55 void deletemov(int id);
     56 void detach(Movement *m);
     57 void detachfltr(Filter *f);
     58 void die(const char *errstr, ...);
     59 void *ecalloc(size_t nmemb, size_t size);
     60 int filtermov(Movement *mov);
     61 int filtermovs(int from, int to, char *txt);
     62 void freemovs(void);
     63 void freefltrs(void);
     64 void loadmovs(void);
     65 void refresh(void);
     66 void savemovs(void);
     67 void showmovs(void);
     68 void sortmovs(void);
     69 int strtoint(char *s);
     70 int strtots(char *s);
     71 void usage(void);
     72 
     73 /* variables */
     74 Movement *movs;
     75 Filter *filters;
     76 Totals totals;
     77 FILE *movsfile;
     78 char movsfilename[256];
     79 int limit = 25;
     80 int filtered = 0;
     81 int nfilters = 0;
     82 
     83 /* function implementations */
     84 int
     85 addmov(char *date, float amount, char *note) {
     86 	Movement *m;
     87 	int id = 0;
     88 
     89 	for(m = movs; m; m = m->next)
     90 		if(m->id > id)
     91 			id = m->id;
     92 	++id;
     93 	m = ecalloc(1, sizeof(Movement));
     94 	m->id = id;
     95 	m->ts = strtots(date);
     96 	m->amount = amount;
     97 	memcpy(m->note, note, sizeof(m->note));
     98 	m->note[ sizeof(m->note) - 1] = '\0';
     99 	attach(m);
    100 	return 0;
    101 }
    102 
    103 void
    104 addfilter(unsigned int type, void *data) {
    105 	Filter *f = ecalloc(1, sizeof(Filter));
    106 
    107 	switch(type) {
    108 	case F_TEXT:
    109 	case F_NOTEXT:
    110 		f->arg.v = data;
    111 		break;
    112 	case F_DATEFROM:
    113 	case F_DATETO:
    114 		f->arg.i = strtots(data);
    115 		break;
    116 	default: die("invalid filter type\n", type);
    117 	}
    118 
    119 	f->type = type;
    120 	attachfltr(f);
    121 }
    122 
    123 void
    124 attach(Movement *m) {
    125 	m->next = movs;
    126 	movs = m;
    127 }
    128 
    129 void
    130 attachfltr(Filter *f) {
    131 	f->next = filters;
    132 	filters = f;
    133 }
    134 
    135 void
    136 attach_sorted_desc(Movement **head, Movement *m) {
    137 	Movement *t;
    138 
    139 	if(!*head || m->ts > (*head)->ts) {
    140 		m->next = *head;
    141 		*head = m;
    142 		return;
    143 	}
    144 	t = *head;
    145 	while(t->next && m->ts <= t->next->ts)
    146 		t = t->next;
    147 	m->next = t->next;
    148 	t->next = m;
    149 }
    150 
    151 void
    152 deletemov(int id) {
    153 	Movement *m;
    154 
    155 	for(m = movs; m; m = m->next)
    156 		if(m->id == id)
    157 			break;
    158 	if(m) {
    159 		detach(m);
    160 		free(m);
    161 	}
    162 }
    163 
    164 void
    165 detach(Movement *m) {
    166 	Movement **tm;
    167 
    168 	for (tm = &movs; *tm && *tm != m; tm = &(*tm)->next);
    169 	*tm = m->next;
    170 }
    171 
    172 void
    173 detachfltr(Filter *f) {
    174 	Filter **tf;
    175 
    176 	for (tf = &filters; *tf && *tf != f; tf = &(*tf)->next);
    177 	*tf = f->next;
    178 }
    179 
    180 void
    181 die(const char *errstr, ...) {
    182 	va_list ap;
    183 
    184 	va_start(ap, errstr);
    185 	vfprintf(stderr, errstr, ap);
    186 	va_end(ap);
    187 	exit(1);
    188 }
    189 
    190 void *
    191 ecalloc(size_t nmemb, size_t size) {
    192 	void *p;
    193 
    194 	if(!(p = calloc(nmemb, size)))
    195 		die("Cannot allocate memory.\n");
    196 	return p;
    197 }
    198 
    199 int
    200 filtermov(Movement *m) {
    201 	Filter *f;
    202 	int ormatch = -1;
    203 
    204 	for(f = filters; f; f = f->next) {
    205 		switch(f->type) {
    206 		case F_TEXT:
    207 			if(ormatch > 0)
    208 				break;
    209 			ormatch = !!strcasestr(m->note, (char *)f->arg.v);
    210 			break;
    211 		case F_NOTEXT:
    212 			if(!!strcasestr(m->note, (char *)f->arg.v))
    213 				return 1;
    214 			break;
    215 		case F_DATEFROM:
    216 			if(!(m->ts >= f->arg.i))
    217 				return 1;
    218 			break;
    219 		case F_DATETO:
    220 			if(!(m->ts <= f->arg.i))
    221 				return 1;
    222 			break;
    223 		}
    224 	}
    225 
    226 	return ormatch == -1 ? 0 : !ormatch;
    227 }
    228 
    229 int
    230 filtermovs(int from, int to, char *txt) {
    231 	Movement *m;
    232 	int n = 0;
    233 
    234 	for(m = movs; m; m = m->next) {
    235 		m->filtered = filtermov(m);
    236 		n += m->filtered;
    237 	}
    238 	return n;
    239 }
    240 
    241 void
    242 freemovs(void) {
    243 	Movement *m;
    244 
    245 	while(movs) {
    246 		m = movs;
    247 		detach(m);
    248 		free(m);
    249 	}
    250 }
    251 
    252 void
    253 freefltrs(void) {
    254 	Filter *f;
    255 
    256 	while(filters) {
    257 		f = filters;
    258 		detachfltr(f);
    259 		free(f);
    260 	}
    261 }
    262 
    263 void
    264 loadmovs(void) {
    265 	Movement *m;
    266 	int r;
    267 
    268 	rewind(movsfile);
    269 	while(1) {
    270 		m = ecalloc(1, sizeof(Movement));
    271 		r = fscanf(movsfile, "%d %d %f %[^\n]", &m->id, &m->ts, &m->amount, &m->note[0]);
    272 		if(r <= 0) {
    273 			if(feof(movsfile))
    274 				break;
    275 			die("%s: file corrupted\n", movsfilename);
    276 		}
    277 		attach(m);
    278        }
    279 }
    280 
    281 void
    282 refresh(void) {
    283 	Movement *m;
    284 
    285 	totals.amount = totals.income = totals.expense = totals.count = 0;
    286 	totals.pamount = totals.pincome = totals.pexpense = totals.pcount = 0;
    287 	for(m = movs; m; m = m->next) {
    288 		totals.amount += m->amount;
    289 		if(m->amount >= 0)
    290 			totals.income += m->amount;
    291 		else
    292 			totals.expense += m->amount;
    293 		++totals.count;
    294 
    295 		if(m->filtered)
    296 			continue;
    297 
    298 		if(totals.pcount >= limit)
    299 			continue;
    300 		totals.pamount += m->amount;
    301 		if(m->amount >= 0)
    302 			totals.pincome += m->amount;
    303 		else
    304 			totals.pexpense += m->amount;
    305 		++totals.pcount;
    306 	}
    307 }
    308 
    309 void
    310 savemovs(void) {
    311 	Movement *m;
    312 
    313 	movsfile = fopen(movsfilename, "w");
    314 	for(m = movs; m; m = m->next)
    315 		fprintf(movsfile, "%d %d %f %s\n", m->id, m->ts, m->amount, m->note);
    316 	fclose(movsfile);
    317 }
    318 
    319 void
    320 showmovs(void) {
    321 	Movement *m;
    322 	time_t ts;
    323 	char time[32];
    324 	int listcount = totals.count - filtered;
    325 	int count = 0;
    326 
    327 	if(limit < listcount)
    328 		listcount = limit;
    329 
    330 	if(listcount)
    331 		printf("%5s | %16s | %8s | %s\n", "id", "date  time", "amount", "note");
    332 	for(m = movs; m && count < limit; m = m->next) {
    333 		if(m->filtered)
    334 			continue;
    335 		++count;
    336 		ts = m->ts;
    337 		strftime(time, sizeof time, "%d/%m/%Y %H:%M", localtime(&ts));
    338 		printf("%5d | %16s | %8.2f | %s\n", m->id, time, m->amount, m->note);
    339 	}
    340 	if(listcount > 1 && listcount < totals.count)
    341 		printf("%5s | %17s: %8.2f | income=%.2f expense=%.2f movements=%d\n", "",
    342 			"Partial", totals.pamount, totals.pincome, totals.pexpense, totals.pcount);
    343 	printf("%5s | %17s: %8.2f | income=%.2f expense=%.2f movements=%d\n",
    344 		"", "Total", totals.amount, totals.income, totals.expense, totals.count);
    345 }
    346 
    347 void
    348 sortmovs(void) {
    349 	Movement *sorted = NULL, *m, *t;
    350 
    351 	m = movs;
    352 	while(m) {
    353 		t = m;
    354 		m = m->next;
    355 		attach_sorted_desc(&sorted, t);
    356 	}
    357 	movs = sorted;
    358 }
    359 
    360 int
    361 strtoint(char *s) {
    362 	long n;
    363 	char *ep;
    364 
    365 	n = strtol(s, &ep, 10);
    366 	if(s == ep || *ep != '\0' || n == LONG_MIN || n == LONG_MAX)
    367 		return -1;
    368 	return (int)n;
    369 }
    370 
    371 int
    372 strtots(char *s) {
    373 	struct tm tm = {.tm_isdst = - 1};
    374 
    375 	if(!strcmp(s, "now"))
    376 		return time(NULL);
    377 	strptime(s, "%d/%m/%Y %H:%M", &tm);
    378 	return mktime(&tm);
    379 }
    380 
    381 void
    382 usage(void) {
    383 	die("Usage: %s [-v] [-defiltx <arg>] [<date [time]> <amount> <note>]\n", argv0);
    384 }
    385 
    386 int
    387 main(int argc, char *argv[]) {
    388 	int delid = 0;
    389 	int from = 0, to = 0;
    390 	int lflag = 0;
    391 	char *txt = NULL;
    392 
    393 	ARGBEGIN {
    394 	case 'd': delid = atoi(EARGF(usage())); break;
    395 	case 'e': addfilter(F_TEXT, EARGF(usage())); break;
    396 	case 'f':
    397 		  addfilter(F_DATEFROM, EARGF(usage()));
    398 		  if(!lflag)
    399 			  limit = 0;
    400 		  break;
    401 	case 'i': snprintf(movsfilename, sizeof movsfilename, "%s", EARGF(usage())); break;
    402 	case 'l':
    403 		  lflag = 1;
    404 		  limit = strtoint(EARGF(usage()));
    405 		  if(limit < 0)
    406 			  die("%s: -l: invalid argument\n", argv0);
    407 		  break;
    408 	case 't':
    409 		  addfilter(F_DATETO, EARGF(usage()));
    410 		  if(!lflag)
    411 			  limit = 0;
    412 		  break;
    413 	case 'v': die("sw-"VERSION"\n");
    414 	case 'x': addfilter(F_NOTEXT, EARGF(usage())); break;
    415 	default: usage();
    416 	} ARGEND;
    417 
    418 	if(!limit)
    419 		limit = INT_MAX;
    420 
    421 	if(!*movsfilename)
    422 		snprintf(movsfilename, sizeof movsfilename, "%s/%s", getenv("HOME"), ".sw");
    423 	movsfile = fopen(movsfilename, "r");
    424 	if(!movsfile)
    425 		die("%s: cannot open the file\n", movsfilename);
    426 	loadmovs();
    427 	fclose(movsfile);
    428 
    429 	if(delid) {
    430 		deletemov(delid);
    431 		savemovs();
    432 		freemovs();
    433 		freefltrs(); /* Only for coherence. We should check incompatible flags anyway */ 
    434 		return 0;
    435 	}
    436 	if(argc) {
    437 		if(argc != 3)
    438 			usage();
    439 		addmov(argv[0], atof(argv[1]), argv[2]);
    440 		savemovs();
    441 		return 0;
    442 	}
    443 	filtered = filtermovs(from, to, txt);
    444 	sortmovs();
    445 	refresh();
    446 	showmovs();
    447 	freemovs();
    448 	freefltrs();
    449 	return 0;
    450 }