1
2 #include <stdlib.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include <time.h>
6 #include "afni_history.h"
7
8 static char g_history[] =
9 {
10 "----------------------------------------------------------------------\n"
11 "history (of afni_history):\n"
12 "\n"
13 "1.0 27 Feb 2008 [rickr] : initial release\n"
14 "\n"
15 "1.1 28 Feb 2008 [rickr] : added -list_authors option\n"
16 "1.2 29 Feb 2008 [rwcox] : separated dates in html output\n"
17 "1.3 29 Feb 2008 [rickr]\n"
18 " - added -type option, to restrict output to a specific type\n"
19 " - added a string to explain the output level\n"
20 " - added TYPE field, and a new SUPERDUPER level\n"
21 "1.4 19 May 2008 [rickr]\n"
22 " - added MY_INT_COMPARE macro for comparison speed-up\n"
23 " - in compare: if strings are not set, ignore rather than claim equal\n"
24 "1.5 25 Jun 2008 [rickr] : added -past_entries option\n"
25 "1.6 14 Jul 2008 [rickr] : single integer arg is used as -past_entries\n"
26 "1.7 29 Mar 2010 [RWCox] : add '-dline' option\n"
27 "1.8 3 Jun 2010 [rickr] : added TYPE_ENHANCE\n"
28 "1.9 19 Jul 2010 [rickr] : added -check_date option\n"
29 "1.10 30 Nov 2010 [rickr] : added -final_sort_by_prog option\n"
30 "1.11 29 Mar 2017 [rickr] : added justin: JKR\n"
31 "1.12 28 Jul 2020 [rickr] : added laurenpd: PDL\n"
32 "1.13 25 Aug 2020 [rickr] : added TYPE_REMOVE, TYPE_REINSTATE\n"
33 "1.14 2 Sep 2020 [rickr] : added -show_type, -show_type_names\n"
34 };
35
36 static char g_version[] = "afni_history version 1.14, 2 Sep 2020";
37
38 static char * g_author_list[] = {
39 "rwcox", "RWC", RWC,
40 "ziad", "ZSS", ZSS,
41 "dglen", "DRG", DRG,
42 "rickr", "RCR", RCR,
43 "gangc", "GC", GC,
44 "christip", "PPC", PPC,
45 "bpittman", "BGP", BGP,
46 "ptaylor", "PT" , PT,
47 "discoraj", "JKR", JKR,
48 "laurenpd", "PDL", PDL
49 };
50
51 /* ======================================================================= */
52 /* field names and defines, for use with the -show_field option
53 *
54 * This is basically a list of fields in the afni_history_struct, in case
55 * one wants to print out just the individual fields, rather than each
56 * entire struct.
57 *
58 * The "date"/DATE entry is a convenience to combine YEAR/MONTH/DAY
59 * (to make chronological == alphabetical).
60 *
61 * This string order must match the non-negative FIELD_* #define's, which
62 * follow. The #define's are here (instead of afni_history.h) to keep
63 * these lists together. */
64
65 static char * g_show_fields[] = {
66 "all", "firstline", /* the rest are (mostly) struct fields */
67 "day", "month", "year", "date",
68 "author", "program", "level", "type",
69 "desc", "verbtext"
70 };
71
72 /* field type values (for show_field) */
73 #define FIELD_INVALID -1 /* bad, naughty type */
74 #define FIELD_ALL 0 /* show all fields */
75 #define FIELD_FIRST_LINE 1 /* show the standard first line */
76 #define FIELD_DAY 2 /* day of month */
77 #define FIELD_MONTH 3 /* month of year */
78 #define FIELD_YEAR 4 /* year of calendar system */
79 #define FIELD_DATE 5 /* year, month, day */
80 #define FIELD_AUTHOR 6 /* author */
81 #define FIELD_PROGRAM 7 /* program */
82 #define FIELD_LEVEL 8 /* level */
83 #define FIELD_TYPE 9 /* type */
84 #define FIELD_DESC 10 /* description */
85 #define FIELD_VERB 11 /* verbtext */
86 #define MAX_FIELD_VAL 12 /* max field index */
87
88 /* end field names and defines */
89 /* ======================================================================= */
90
91
92 histpair g_histpairs[NUM_HIST_USERS]; /* will initialize internally */
93
94 /* use this for top-level access */
95 global_data GD;
96
main(int argc,char * argv[])97 int main(int argc, char *argv[])
98 {
99 global_data * gd = &GD;
100 hist_type ** hlist = NULL;
101 int rv, hlen = 0;
102
103 /* check -show_field entries */
104 if ( quick_field_test() ) return 1;
105
106 rv = process_options(argc, argv, gd);
107 if( rv < 0 ) return 1;
108 else if( rv > 0 ) return 0; /* gentle exit */
109
110 /* fill hist people's history_structs */
111 if( init_histlist(gd) ) return 1;
112
113 /* are they valid? */
114 if( ! histlists_are_valid(gd->histpairs, gd->plen) ) return 1;
115
116 /* restrict results based on options 19 Jul 2010 [rickr] */
117 rv = restrict_results(gd, &hlist, &hlen);
118 if( rv ) return rv;
119
120 /* either check the date or return history info */
121 if( gd->check_date ) rv = check_date(gd, hlist, hlen);
122 else rv = show_history(gd, hlist, hlen);
123
124 if( hlist ) free(hlist);
125
126 return rv;
127 }
128
129 /* return -1 on error, +1 for gentle exit, and 0 to continue */
process_options(int argc,char * argv[],global_data * gd)130 int process_options(int argc, char * argv[], global_data * gd)
131 {
132 int ac;
133
134 if( argc < 0 || !argv || !gd ) return -1;
135
136 memset(gd, 0, sizeof(global_data));
137 gd->sort_dir = 1; /* can reverse this */
138 gd->verb = 1;
139 gd->cd_day = NULL; gd->cd_month = NULL; gd->cd_year = NULL;
140
141
142 /* if( argc <= 1 ) { show_help(); return 1; } maybe just run */
143
144 for( ac = 1; ac < argc; ac++ ) {
145 /* early quitters, then the rest alphabetically */
146 if( !strcmp(argv[ac], "-help") ) {
147 show_help();
148 return 1;
149 } else if( !strcmp(argv[ac], "-hist") ) {
150 puts(g_history);
151 return 1;
152 } else if( !strcmp(argv[ac], "-list_authors") ) {
153 show_author_list();
154 return 1;
155 } else if( !strcmp(argv[ac], "-show_field_names" ) ) {
156 show_valid_fields();
157 return 1;
158 } else if( !strcmp(argv[ac], "-list_types") ) {
159 show_valid_types();
160 return 1;
161 } else if( !strcmp(argv[ac], "-ver") ) {
162 puts(g_version);
163 return 1;
164 /* alphabetical continuation */
165 } else if( !strcmp(argv[ac], "-author" ) ) {
166 ac++;
167 CHECK_NEXT_OPT2(ac, argc, "-author", "AUTHOR");
168 gd->author = convert_author(argv[ac]);
169 } else if( !strcmp(argv[ac], "-check_date" ) ) {
170 if( ac+3 >= argc ) {
171 fprintf(stderr,"** -check_date: requiers DD MM YYYY params\n");
172 return -1;
173 }
174 gd->check_date = 1;
175 gd->cd_day = argv[++ac];
176 gd->cd_month = argv[++ac];
177 gd->cd_year = argv[++ac];
178 } else if( !strcmp(argv[ac], "-dline" ) ) {
179 gd->html = 0; gd->dline = 1;
180 } else if( !strcmp(argv[ac], "-html" ) ) {
181 gd->html = 1; gd->dline = 0;
182 } else if( !strcmp(argv[ac], "-level" ) ) {
183 ac++;
184 CHECK_NEXT_OPT2(ac, argc, "-level", "LEVEL");
185 gd->level = atol(argv[ac]);
186 } else if( !strcmp(argv[ac], "-min_level" ) ) {
187 ac++;
188 CHECK_NEXT_OPT2(ac, argc, "-min_level", "LEVEL");
189 gd->min_level = atol(argv[ac]);
190 } else if( !strcmp(argv[ac], "-past_days" ) ) {
191 ac++;
192 CHECK_NEXT_OPT2(ac, argc, "-past_days", "DAYS");
193 gd->past_days = atol(argv[ac]);
194 if( gd->past_days < 0 ) {
195 fprintf(stderr,"** DAYS should be at least zero\n");
196 return 1;
197 }
198 } else if( !strcmp(argv[ac], "-past_entries" ) ) {
199 ac++;
200 CHECK_NEXT_OPT2(ac, argc, "-past_entries", "ENTRIES");
201 gd->past_entries = atol(argv[ac]);
202 if( gd->past_entries < 1 ) {
203 fprintf(stderr,"** ENTRIES should be at least one\n");
204 return 1;
205 }
206 } else if( !strcmp(argv[ac], "-past_months" ) ) {
207 ac++;
208 CHECK_NEXT_OPT2(ac, argc, "-past_months", "MONTHS");
209 gd->past_months = atol(argv[ac]);
210 if( gd->past_months < 0 ) {
211 fprintf(stderr,"** MONTHS should be at least zero\n");
212 return 1;
213 }
214 } else if( !strcmp(argv[ac], "-past_years" ) ) {
215 ac++;
216 CHECK_NEXT_OPT2(ac, argc, "-past_years", "YEARS");
217 gd->past_years = atol(argv[ac]);
218 if( gd->past_years < 0 ) {
219 fprintf(stderr,"** YEARS should be at least zero\n");
220 return 1;
221 }
222 } else if( !strcmp(argv[ac], "-program" ) ) {
223 ac++;
224 CHECK_NEXT_OPT2(ac, argc, "-program", "PROGRAM");
225 gd->program = argv[ac];
226 } else if( !strcmp(argv[ac], "-reverse" ) ) {
227 gd->sort_dir = -1;
228 } else if( !strcmp(argv[ac], "-final_sort_by_prog" ) ) {
229 gd->final_sort_by_prog = 1;
230 } else if( !strcmp(argv[ac], "-show_field" ) ) {
231 ac++;
232 CHECK_NEXT_OPT2(ac, argc, "-show_field", "FIELD");
233 gd->show_field = field_name2index(argv[ac]);
234 if( gd->show_field < 0 ) {
235 fprintf(stderr,"** invalid show field '%s'\n", argv[ac]);
236 fprintf(stderr," see 'afni_history -show_field_names'\n");
237 return -1;
238 }
239 } else if( !strcmp(argv[ac], "-type" ) ) {
240 ac++;
241 CHECK_NEXT_OPT2(ac, argc, "-type", "TYPE");
242 if( isdigit(*argv[ac]) )
243 gd->type = atol(argv[ac]); /* integer */
244 else
245 gd->type = type_string2type(argv[ac]); /* string */
246 if( ! INT_IN_RANGE(gd->type, 0, MAX_TYPE_VAL) ) {
247 fprintf(stderr,"** invalid type (number or string): %s\n",
248 argv[ac]);
249 return -1;
250 }
251 } else if( !strcmp(argv[ac], "-verb") ) {
252 ac++;
253 CHECK_NEXT_OPT2(ac, argc, "-verb", "LEVEL");
254 gd->verb = atoi(argv[ac]);
255 /* last possibility, check for shortcut like "afni_history 3" */
256 } else if( (ac == argc-1) && isdigit(argv[ac][0]) ) {
257 gd->past_entries = atol(argv[ac]);
258 if( gd->past_entries < 1 ) {
259 fprintf(stderr,"** invalid last option: '%s'\n", argv[ac]);
260 return 1;
261 }
262 } else {
263 fprintf(stderr,"** invalid option: '%s'\n", argv[ac]);
264 return -1;
265 }
266 }
267
268 if( gd->verb > 2 ) disp_global_data("-- options read: ", gd);
269
270 /* save options for later, in case we want to print them out */
271 gd->argc = argc;
272 gd->argv = argv;
273
274 return 0;
275 }
276
convert_author(char * name)277 char * convert_author(char * name)
278 {
279 if( !name ) return NULL;
280
281 if( !strcmp(name, "RWC") ) return RWC;
282 if( !strcmp(name, "rwcox") ) return RWC;
283 if( !strcmp(name, "bob") ) return RWC;
284 if( !strcmp(name, "Bob") ) return RWC;
285
286 if( !strcmp(name, "ZSS") ) return ZSS;
287 if( !strcmp(name, "Ziad") ) return ZSS;
288 if( !strcmp(name, "ziad") ) return ZSS;
289
290 if( !strcmp(name, "PT") ) return PT;
291 if( !strcmp(name, "Paul") ) return PT;
292 if( !strcmp(name, "PTaylor") ) return PT;
293 if( !strcmp(name, "ptaylor") ) return PT;
294
295 if( !strcmp(name, "DRG") ) return DRG;
296 if( !strcmp(name, "dglen") ) return DRG;
297 if( !strcmp(name, "Daniel") ) return DRG;
298 if( !strcmp(name, "daniel") ) return DRG;
299
300 if( !strcmp(name, "RCR") ) return RCR;
301 if( !strcmp(name, "rickr") ) return RCR;
302 if( !strcmp(name, "Rick") ) return RCR;
303 if( !strcmp(name, "rick") ) return RCR;
304
305 if( !strcmp(name, "GC") ) return GC;
306 if( !strcmp(name, "gangc") ) return GC;
307 if( !strcmp(name, "Gang") ) return GC;
308 if( !strcmp(name, "gang") ) return GC;
309
310 if( !strcmp(name, "PPC") ) return PPC;
311 if( !strcmp(name, "christip") ) return PPC;
312 if( !strcmp(name, "Peggy") ) return PPC;
313 if( !strcmp(name, "peggy") ) return PPC;
314
315 if( !strcmp(name, "BGP") ) return BGP;
316 if( !strcmp(name, "bpittman") ) return BGP;
317 if( !strcmp(name, "pittmanb") ) return BGP;
318 if( !strcmp(name, "Brian") ) return BGP;
319 if( !strcmp(name, "brian") ) return BGP;
320
321 if( !strcmp(name, "JKR") ) return JKR;
322 if( !strcmp(name, "discoraj") ) return JKR;
323 if( !strcmp(name, "justin") ) return JKR;
324 if( !strcmp(name, "Justin") ) return JKR;
325 if( !strcmp(name, "rajendrajk"))return JKR;
326
327 if( !strcmp(name, "PDL") ) return PDL;
328 if( !strcmp(name, "laurenpd") ) return PDL;
329 if( !strcmp(name, "Peter") ) return PDL;
330 if( !strcmp(name, "peter") ) return PDL;
331
332 return name; /* give up and stick with what we have */
333 }
334
335 /* if author is set, results are for only that author
336 * otherwise, create a combined list, return it and its length
337 */
restrict_results(global_data * gd,hist_type *** H,int * Hlen)338 int restrict_results(global_data * gd, hist_type *** H, int * Hlen)
339 {
340 hist_type ** hlist = NULL, * h1 = NULL;
341 int c, rv = 0, hlen = 0;
342
343 if( gd->author ) {
344 if( gd->verb > 3 )
345 fprintf(stderr,"-- searching for history of '%s'\n", gd->author);
346
347 for( c = 0; c < gd->plen; c++ )
348 if( ! strcmp(gd->histpairs[c].author, gd->author) )
349 break;
350
351 if( c >= gd->plen ) {
352 fprintf(stderr,"** could not find history for author '%s'\n",
353 gd->author);
354 return 1;
355 }
356
357 if( gd->verb > 1 )
358 fprintf(stderr,"++ found history for author '%s'\n", gd->author);
359
360 /* allocate hlist, and set pointers */
361 h1 = gd->histpairs[c].hlist;
362 if( add_to_hlist(&hlist, h1, hlist_len(h1), &hlen) ) return 1;
363 } else {
364 if( gd->verb > 3 ) fprintf(stderr,"-- compiling composite history\n");
365
366 /* no author, so create hlist as a composite list */
367
368 for( c = 0; c < gd->plen; c++ ) {
369 h1 = gd->histpairs[c].hlist;
370 if( gd->verb > 3 )
371 fprintf(stderr,"\n++ adding history for author '%s'\n",
372 gd->histpairs[c].author);
373 if( add_to_hlist(&hlist, h1, hlist_len(h1), &hlen) ) {
374 if( hlist ) free(hlist);
375 return 1;
376 }
377 }
378 }
379
380 /* maybe restrict the list to a specific program */
381 if( !rv && gd->program )
382 rv = restrict_by_program(gd, &hlist, &hlen);
383
384 /* maybe restrict the list to a level or a set of levels */
385 if( !rv && (gd->level || gd->min_level) )
386 rv = restrict_by_level(gd, &hlist, &hlen);
387
388 /* maybe restrict the list to a level or a set of levels */
389 if( !rv && gd->type )
390 rv = restrict_by_type(gd, &hlist, &hlen);
391
392 /* sort by date, author, level and program */
393 if( !rv && hlen > 0 )
394 qsort(hlist, hlen, sizeof(hist_type *), compare_hlist);
395
396 /* maybe restrict by the date */
397 if( !rv && hlen>0 && (gd->past_days || gd->past_months || gd->past_years) )
398 rv = restrict_by_date(gd, &hlist, &hlen);
399
400 /* to show the past_entries, restrict the indices based on sort_dir */
401 /* 25 Jun 2008 [rickr] */
402 if( gd->past_entries > 0 && gd->past_entries < hlen ) {
403 int first = 0;
404 if( gd->sort_dir == 1 ) first = hlen - gd->past_entries;
405
406 if( gd->verb > 1 )
407 fprintf(stderr,"++ restricting to entires from %d to %d\n",
408 hlen, gd->past_entries);
409
410 if( restrict_hlist(&hlist, first, hlen, gd->past_entries) ) return 1;
411
412 hlen = gd->past_entries;
413 }
414
415 /* maybe resort by program name first */
416 if( !rv && hlen > 0 && gd->final_sort_by_prog ) {
417 gd->sort_by_prog = 1;
418 qsort(hlist, hlen, sizeof(hist_type *), compare_hlist);
419 gd->sort_by_prog = 0;
420 }
421
422 *H = hlist;
423 *Hlen = hlen;
424
425 return 0;
426 }
427
428 /* check whether there is an entry as recent as the user date
429 *
430 * if so, return 0 (good)
431 * else, return 1 (bad)
432 */
check_date(global_data * gd,hist_type ** hlist,int len)433 int check_date(global_data * gd, hist_type ** hlist, int len)
434 {
435 hist_type * hh = NULL;
436 int yy, mm, dd;
437
438 if( gd->verb > 1 )
439 fprintf(stderr,"\n-- checking date, length %d...\n\n",len);
440
441 if( !hlist || !*hlist || len <= 0 ) {
442 if( gd->verb > 0 )
443 printf("no history to check, given the restrictions\n");
444 return 1;
445 }
446
447 if( set_dates(gd, &dd, &mm, &yy) ) return 1;
448
449 if( gd->sort_dir ) hh = hlist[len-1]; /* last is most recent */
450 else hh = hlist[0];
451
452 if( gd->verb > 1 ) {
453 fprintf(stderr,"-- comparing date against hist entry:\n");
454 show_hist_entry(hh, stderr, FIELD_ALL);
455 }
456
457 if( hh->yyyy < yy ||
458 (hh->yyyy == yy && hh->mm < mm ) ||
459 (hh->yyyy == yy && hh->mm == mm && hh->dd < dd ) ) {
460 if( gd->verb )
461 printf("** too old: afni_history older than: %02d %s %04d\n"
462 " most recent entry is: %02d %s %04d\n",
463 dd, mm2month(mm), yy, hh->dd, mm2month(hh->mm), hh->yyyy);
464 return 1;
465 }
466
467 if( gd->verb )
468 printf("-- is current: afni_history as new as: %02d %s %04d\n"
469 " most recent entry is: %02d %s %04d\n",
470 dd, mm2month(mm), yy, hh->dd, mm2month(hh->mm), hh->yyyy);
471
472 return 0;
473 }
474
475 /* extract and validate DD/MM/YY values from the user options
476 * return 0 if they seem okay, 1 otherwise */
set_dates(global_data * gd,int * dd,int * mm,int * yy)477 int set_dates(global_data * gd, int * dd, int * mm, int * yy )
478 {
479 int rv = 0;
480
481 if( !gd->cd_day || !gd->cd_month || !gd->cd_year ) {
482 fprintf(stderr,"** set_dates: dd/mm/yy not set: %p, %p, %p\n",
483 gd->cd_day, gd->cd_month, gd->cd_year);
484 return 1;
485 }
486
487 *dd = atoi(gd->cd_day);
488 *mm = month2mm(gd->cd_month); /* if month is a bad string, get as int */
489 if(*mm <= 0 || *mm > 12) *mm = atoi(gd->cd_month);
490 *yy = atoi(gd->cd_year);
491
492 if( gd->verb > 1 )
493 fprintf(stderr,"\n-- set dates, dd/mm/yy = %d/%d/%d\n", *dd, *mm, *yy);
494
495 if( *dd < 0 || *dd > 31 ) {
496 fprintf(stderr,"** bad day: %d\n", *dd);
497 rv = 1;
498 }
499 if( *mm < 0 || *mm > 12 ) {
500 fprintf(stderr,"** bad month: %d\n", *mm);
501 rv = 1;
502 }
503 if( *yy < 0 || *yy > 3000 ) {
504 fprintf(stderr,"** bad year: %d\n", *yy);
505 rv = 1;
506 }
507
508 return rv;
509 }
510
511 /* hlist is an array of structure pointers */
show_history(global_data * gd,hist_type ** hlist,int len)512 int show_history(global_data * gd, hist_type ** hlist, int len)
513 {
514 int c, first = 0; /* use first for truncating on past_entries */
515
516 if( gd->verb > 1 )
517 fprintf(stderr,"\n-- showing history, length %d...\n\n",len);
518
519 if( gd->html ) show_html_header(gd, stdout, gd->min_level);
520
521 if( !hlist || !*hlist || len <= 0 ) {
522 if( gd->verb > 0 )
523 printf("no history to show, given the restrictions\n");
524 if( gd->html ) show_html_footer(stdout);
525 return 1;
526 }
527
528 #if 0
529 /* to show the past_entries, restrict the indices based on sort_dir */
530 /* 25 Jun 2008 [rickr] */
531 if( gd->past_entries > 0 && gd->past_entries < len ) {
532 if( gd->sort_dir == 1 ) first = len - gd->past_entries;
533 else len = gd->past_entries;
534 if( gd->verb > 1 )
535 fprintf(stderr,"++ restricting to entires from %d to %d\n",
536 first, len-1);
537 }
538 #endif
539
540 if( gd->verb > 0 ) {
541 if( gd->sort_dir == 1 )
542 printf(" ---- log of AFNI updates (most recent last) ----\n\n");
543 else
544 printf(" ---- log of AFNI updates (most recent first) ----\n\n");
545 }
546
547 for( c = first; c < len; c++ ) {
548 if( c > 0 && compare_hist_dates(hlist[c],hlist[c-1]) ){
549 if( gd->html )
550 show_html_separator(stdout) ; /* RWC */
551 else if( gd->dline )
552 show_dline_separator(stdout) ; /* RWC */
553 }
554
555 show_hist_entry(hlist[c], stdout, gd->show_field);
556 }
557
558 if( gd->html ) show_html_footer(stdout);
559
560 return 0;
561 }
562
563 /* special fields are FIELD_FIRST_LINE and other (field > FIELD_ALL),
564 * else show as FIELD_ALL */
show_hist_entry(hist_type * hp,FILE * fp,int field)565 int show_hist_entry(hist_type * hp, FILE * fp, int field)
566 {
567 /* if the user wants only 1 field, show it and return */
568 /* (individual fields come after FIELD_ALL and FIELD_FIRST_LINE) */
569 if( field > FIELD_FIRST_LINE && field <= MAX_FIELD_VAL ) {
570 if( field == FIELD_DAY ) fprintf(fp, "%02d\n", hp->dd);
571 if( field == FIELD_MONTH ) fprintf(fp, "%s\n", mm2month(hp->mm));
572 if( field == FIELD_YEAR ) fprintf(fp, "%04d\n", hp->yyyy);
573
574 /* DATE is a special field - show a full date */
575 if( field == FIELD_DATE )
576 fprintf(fp, "%04d %02d %02d\n",
577 hp->yyyy, hp->mm, hp->dd);
578
579 if( field == FIELD_AUTHOR ) fprintf(fp, "%s\n", hp->author);
580 if( field == FIELD_PROGRAM ) fprintf(fp, "%s\n", hp->program);
581 if( field == FIELD_LEVEL ) fprintf(fp, "%d (%s)\n",
582 hp->level, level_string(hp->level));
583 if( field == FIELD_TYPE ) fprintf(fp, "%d (%s)\n",
584 hp->type, type_string(hp->type));
585 if( field == FIELD_DESC ) fprintf(fp, "%s\n", hp->desc);
586 if( field == FIELD_VERB ) fprintf(fp, "%s\n", hp->verbtext);
587
588 /* we are done with the single entries */
589 return 0;
590 }
591
592 /* at this point, we want just the first line, or the entire struct */
593
594 /* print the first line, either way */
595 fprintf(fp, "%02d %s %04d, %s, %s, level %d (%s), type %d (%s)\n",
596 hp->dd, mm2month(hp->mm), hp->yyyy,
597 hp->author, hp->program, hp->level, level_string(hp->level),
598 hp->type, type_string(hp->type));
599
600 /* if the user wants only the first line, we are done */
601 if( field == FIELD_FIRST_LINE )
602 return 0;
603
604
605 /* otherwise, print the rest of the struct */
606
607 fprintf(fp, " %s\n", hp->desc);
608 if( hp->desc[strlen(hp->desc)-1] != '\n' ) fputc('\n', fp);
609
610 if( hp->verbtext )
611 show_wrapping_line(hp->verbtext, " ", 4, fp);
612
613 return 0;
614 }
615
616 /* apply line indent per line, if we exceed MAX_LINE_CHARS, wrap */
show_wrapping_line(char * str,char * prefix,int indent,FILE * fp)617 int show_wrapping_line(char * str, char * prefix, int indent, FILE * fp)
618 {
619 int c, cline, len;
620
621 if( !str ) return 0;
622
623 if( prefix ) fputs(prefix, fp);
624
625 len = strlen(str);
626 if( len < 2 ) return 1;
627
628 if( str[len-1] == '\n' ) len--; /* ignore trailing newline */
629
630 cline = 0;
631 for( c = 0; c < len; c++ ) {
632 if( str[c] == '\n' ) { /* print newline and indent */
633 fputc('\n', fp);
634 fprintf(fp, "%*s", indent, "");
635 cline = 0;
636 continue;
637 } else if ( cline > MAX_LINE_CHARS ) { /* fix, and continue */
638 fputc('\n', fp);
639 fprintf(fp, "%*s", indent, "");
640 cline = 0;
641 }
642 fputc(str[c], fp);
643 cline++;
644 }
645
646 fprintf(fp,"\n\n");
647
648 return 0;
649 }
650
show_html_header(global_data * gd,FILE * fp,int min_level)651 int show_html_header(global_data * gd, FILE * fp, int min_level)
652 {
653 fprintf(fp, "<html><head>\n"
654 "<title>AFNI HISTORY</title></head>\n"
655 "<body><center><h1>AFNI HISTORY</h1>\n");
656
657 if( min_level > 0 )
658 fprintf(fp, "<h2>level %d and higher</h2>\n", min_level);
659 else
660 fprintf(fp, "<h2>all levels</h2>\n");
661
662 fprintf(fp,"</center><h4><pre>\n"
663 "The levels of importance go from 1 to 5, with meanings:\n"
664 " 1 - users would not care\n"
665 " 2 - of little importance, though some users might care\n"
666 " 3 - fairly important\n"
667 " 4 - a big change or new program\n"
668 " 5 - IMPORTANT: we expect users to know\n"
669 "\n</h4>\n");
670
671 fprintf(fp, "</center><hr />\n<pre>\n");
672 fprintf(fp, "<b>generated via the command : ");
673 show_command(fp, gd->argc, gd->argv);
674 fprintf(fp, "</b><hr width='75%%' align='left'/>\n");
675
676 return 0;
677 }
678
show_command(FILE * fp,int argc,char ** argv)679 int show_command(FILE * fp, int argc, char ** argv)
680 {
681 int c;
682
683 for( c = 0; c < argc; c++ )
684 fprintf(fp, "%s ", argv[c]);
685 fputc('\n', fp);
686
687 return 0;
688 }
689
show_html_footer(FILE * fp)690 int show_html_footer(FILE * fp)
691 {
692 fprintf(fp, "</pre>\n"
693 "<hr /><b>auto-generated by afni_history on %s</b>\n",
694 __DATE__);
695 fprintf(fp, "</body></html>\n");
696
697 return 0;
698 }
699
show_html_separator(FILE * fp)700 int show_html_separator(FILE * fp) /* RWC */
701 {
702 fprintf(fp, "<hr width='50%%' align='left' />\n") ;
703
704 return 0 ;
705 }
706
show_dline_separator(FILE * fp)707 int show_dline_separator(FILE * fp) /* RWC */
708 {
709 fprintf(fp,
710 "----------------------------------------------------------------------\n\n");
711 return 0 ;
712 }
713
714
715 /* convert a numerical month to a readable word */
mm2month(int mm)716 char * mm2month(int mm)
717 {
718 if( mm <= 0 ) return "ILLEGAL";
719 if( mm == 1 ) return "Jan";
720 if( mm == 2 ) return "Feb";
721 if( mm == 3 ) return "Mar";
722 if( mm == 4 ) return "Apr";
723 if( mm == 5 ) return "May";
724 if( mm == 6 ) return "Jun";
725 if( mm == 7 ) return "Jul";
726 if( mm == 8 ) return "Aug";
727 if( mm == 9 ) return "Sep";
728 if( mm == 10 ) return "Oct";
729 if( mm == 11 ) return "Nov";
730 if( mm == 12 ) return "Dec";
731
732 return "ILLEGAL";
733 }
734
735 /* convert a numerical month to a readable word */
month2mm(char * month)736 int month2mm(char * month)
737 {
738 char * copy;
739 int cc, rv = 0, len;
740
741 if( !month || !*month ) return 0;
742
743 /* make a lower case copy (no libraries used so far, keep it that way) */
744 len = strlen(month);
745 copy = (char *)malloc((len+1)*sizeof(char));
746 strcpy(copy, month);
747 if( !copy ) {
748 fprintf(stderr,"** month2mm: failed to copy '%s'\n", month);
749 return 0;
750 }
751 for(cc = 0; cc < strlen(copy); cc++) copy[cc] = tolower(copy[cc]);
752
753 if ( ! strncmp(copy, "jan", 3) ) rv = 1;
754 else if( ! strncmp(copy, "feb", 3) ) rv = 2;
755 else if( ! strncmp(copy, "mar", 3) ) rv = 3;
756 else if( ! strncmp(copy, "apr", 3) ) rv = 4;
757 else if( ! strncmp(copy, "may", 3) ) rv = 5;
758 else if( ! strncmp(copy, "jun", 3) ) rv = 6;
759 else if( ! strncmp(copy, "jul", 3) ) rv = 7;
760 else if( ! strncmp(copy, "aug", 3) ) rv = 8;
761 else if( ! strncmp(copy, "sep", 3) ) rv = 9;
762 else if( ! strncmp(copy, "oct", 3) ) rv = 10;
763 else if( ! strncmp(copy, "nov", 3) ) rv = 11;
764 else if( ! strncmp(copy, "dec", 3) ) rv = 12;
765
766 if(copy) free(copy);
767
768 return rv;
769 }
770
771 /* perhaps we want to remove everything that is not so recent */
restrict_by_date(global_data * gd,hist_type *** hlist,int * len)772 int restrict_by_date(global_data * gd, hist_type *** hlist, int * len)
773 {
774 hist_type ** hptr, hstr, *hsptr;
775 struct tm * loctime; /* to get todays date */
776 time_t tsec;
777 long long offset;
778 int c, gap, nfound, dcount;
779
780 if( !hlist || !*hlist || !len || *len <= 0 ) {
781 if( gd->verb > 1 ) fprintf(stderr,"** bad, evil restriction\n");
782 return 1;
783 }
784
785 /* be sure we have exactly 1 operation here */
786 dcount = 0;
787 if( gd->past_days > 0 ) dcount++;
788 if( gd->past_months > 0 ) dcount++;
789 if( gd->past_years > 0 ) dcount++;
790 if( dcount < 1 ) {
791 if(gd->verb > 1) fprintf(stderr,"-- no date restriction to enforce\n");
792 return 0;
793 } else if ( dcount > 1 ) {
794 fprintf(stderr,"** will not apply multiple date restrictions\n");
795 return 1;
796 }
797
798 /* get time in seconds, and subtract the offset to get a comparison date */
799 offset = 24 * 3600; /* seconds in a day */
800 if( gd->past_days > 0 ) offset *= gd->past_days;
801 if( gd->past_months > 0 ) offset *= (30.5 * gd->past_months);
802 if( gd->past_years > 0 ) offset *= (365.25 * gd->past_years);
803 tsec = time(NULL);
804 if( gd->verb > 1 )
805 fprintf(stderr,"++ converting current date: %s\n",ctime(&tsec));
806 tsec -= offset;
807 loctime = localtime(&tsec);
808 if( gd->verb > 1 )
809 fprintf(stderr," to cutoff date: %s\n",ctime(&tsec));
810
811 memset(&hstr, 0, sizeof(hstr)); /* clear and init */
812 hstr.dd = loctime->tm_mday; /* 1..31 */
813 hstr.mm = loctime->tm_mon + 1; /* from 0..11 */
814 hstr.yyyy = loctime->tm_year + 1900; /* was offset from 1900 */
815 hsptr = &hstr; /* needed for compar() */
816
817 if( gd->verb > 1 )
818 fprintf(stderr,"++ applying cutoff date of dd/mm/yyyy %02d/%02d/%04d\n",
819 hstr.dd, hstr.mm, hstr.yyyy);
820
821 /* find a cutoff, and move everything to the top */
822 nfound = 0;
823 hptr = *hlist;
824 for( c = 0; c < *len; c++ ) {
825 /* equality is good in the reverse direction */
826 if( gd->sort_dir < 0 && compare_hlist(&hsptr, hptr+c) == 0 ) continue;
827 if( compare_hlist(&hsptr, hptr+c) <= 0 ) break;
828 }
829
830 /* results will be backward, depending on sort */
831 if( gd->sort_dir == 1 ) {
832 nfound = *len - c;
833 gap = c;
834 if( nfound > 0 && nfound < *len ) {
835 if(gd->verb > 2)
836 fprintf(stderr,"-- shifting histlist up by %d\n",gap);
837 for( c = 0; c < nfound; c++ ) /* shift lower entries to the top */
838 hptr[c] = hptr[c+gap];
839 }
840 } else
841 nfound = c; /* no shift requred */
842
843 if( nfound == 0 ) { /* death by 'levels' */
844 if(gd->verb>0) fprintf(stderr,"-- no history for date restriction\n");
845 free(*hlist);
846 *hlist = NULL;
847 return 1;
848 }
849
850 /* restrict the list */
851 if( nfound < *len ) {
852 *hlist = (hist_type **)realloc(*hlist, nfound*sizeof(hist_type *));
853 if( !*hlist ) {
854 fprintf(stderr,"** failed realloc of list ptrs to len %d\n",nfound);
855 return 1;
856 }
857 }
858
859 if( gd->verb > 1 )
860 fprintf(stderr,"++ date restriction drops list length from %d to %d\n",
861 *len, nfound);
862
863 *len = nfound;
864
865 return 0;
866 }
867
868 /* perhaps we want to remove everything that is not our 'type' */
restrict_by_type(global_data * gd,hist_type *** hlist,int * len)869 int restrict_by_type(global_data * gd, hist_type *** hlist, int * len)
870 {
871 hist_type ** hptr; /* just for typing */
872 int c, nfound;
873
874 if( !hlist || !*hlist || !len || *len <= 0 ) {
875 if( gd->verb > 1 ) fprintf(stderr,"** RBT: bad restriction\n");
876 return 1;
877 }
878
879 if( !gd->type ) {
880 if( gd->verb > 3 ) fprintf(stderr, "-- no type to restrict to\n");
881 return 0;
882 }
883
884 /* move good pointers to beginning of list, and count num found */
885 nfound = 0;
886 hptr = *hlist;
887 for( c = 0; c < *len; c++) {
888 if( gd->type == hptr[c]->type ) {
889 if( c > nfound ) hptr[nfound] = hptr[c]; /* move it up */
890 nfound++;
891 }
892 }
893
894 if( nfound == 0 ) { /* death by 'type' */
895 if(gd->verb>0) fprintf(stderr,"-- no history for type restriction\n");
896 free(*hlist);
897 *hlist = NULL;
898 return 1;
899 }
900
901 /* restrict the list */
902 *hlist = (hist_type **)realloc(*hlist, nfound*sizeof(hist_type *));
903 if( !*hlist ) {
904 fprintf(stderr,"** failed to realloc hlist ptrs to len %d\n", nfound);
905 return 1;
906 }
907
908 if( gd->verb > 1 )
909 fprintf(stderr,"++ type %d drops list length from %d to %d\n",
910 gd->type, *len, nfound);
911
912 *len = nfound;
913
914 return 0;
915 }
916
917 /* perhaps we want to remove everything that is not up to our 'level' */
restrict_by_level(global_data * gd,hist_type *** hlist,int * len)918 int restrict_by_level(global_data * gd, hist_type *** hlist, int * len)
919 {
920 hist_type ** hptr; /* just for typing */
921 int c, nfound;
922
923 if( !hlist || !*hlist || !len || *len <= 0 ) {
924 if( gd->verb > 1 ) fprintf(stderr,"** bad, naughty restriction\n");
925 return 1;
926 }
927
928 if( !gd->level && !gd->min_level ) {
929 if( gd->verb > 3 ) fprintf(stderr, "-- no levels to restrict to\n");
930 return 0;
931 }
932
933 /* move good pointers to beginning of list, and count num found */
934 nfound = 0;
935 hptr = *hlist;
936 for( c = 0; c < *len; c++) {
937 if( (gd->level && gd->level != hptr[c]->level) ||
938 (hptr[c]->level < gd->min_level) ) continue; /* skip it */
939
940 /* we have a winner! */
941 if( c > nfound ) hptr[nfound] = hptr[c]; /* move it up */
942 nfound++;
943 }
944
945 if( nfound == 0 ) { /* death by 'levels' */
946 if(gd->verb>0) fprintf(stderr,"-- no history for level restriction\n");
947 free(*hlist);
948 *hlist = NULL;
949 return 1;
950 }
951
952 /* restrict the list */
953 *hlist = (hist_type **)realloc(*hlist, nfound*sizeof(hist_type *));
954 if( !*hlist ) {
955 fprintf(stderr,"** failed to realloc hlist ptrs to len %d\n", nfound);
956 return 1;
957 }
958
959 if( gd->verb > 1 ) {
960 if( gd->level )
961 fprintf(stderr,"++ level %d drops list length from %d to %d\n",
962 gd->level, *len, nfound);
963 else
964 fprintf(stderr,"++ min_level %d drops list length from %d to %d\n",
965 gd->min_level, *len, nfound);
966 }
967
968 *len = nfound;
969
970 return 0;
971 }
972
973 /* perhaps we want to remove everything that is not our program of interest */
restrict_by_program(global_data * gd,hist_type *** hlist,int * len)974 int restrict_by_program(global_data * gd, hist_type *** hlist, int * len)
975 {
976 hist_type ** hptr; /* just for typing */
977 int c, nfound;
978
979 if( !hlist || !*hlist || !len || *len <= 0 ) {
980 if( gd->verb > 1 ) fprintf(stderr,"** evil restriction\n");
981 return 1;
982 }
983
984 if( !gd->program ) {
985 if( gd->verb > 3 ) fprintf(stderr, "-- no program to restrict to\n");
986 return 0;
987 }
988
989 /* move good pointers to beginning of list, and count num found */
990 nfound = 0;
991 hptr = *hlist;
992 for( c = 0; c < *len; c++) {
993 if( ! strcmp(gd->program, hptr[c]->program) ) {
994 /* we have a winner! */
995 if( c > nfound ) hptr[nfound] = hptr[c]; /* move it up */
996 nfound++;
997 }
998 }
999
1000 if( nfound == 0 ) { /* death by 'program' */
1001 if( gd->verb > 0 )
1002 fprintf(stderr,"-- no history for program '%s'\n", gd->program);
1003 free(*hlist);
1004 *hlist = NULL;
1005 return 1;
1006 }
1007
1008 /* restrict the list */
1009 *hlist = (hist_type **)realloc(*hlist, nfound*sizeof(hist_type *));
1010 if( !*hlist ) {
1011 fprintf(stderr,"** failed to realloc hlist ptrs to len %d\n", nfound);
1012 return 1;
1013 }
1014
1015 if( gd->verb > 1 )
1016 fprintf(stderr,"++ program '%s' drops list length from %d to %d\n",
1017 gd->program, *len, nfound);
1018
1019 *len = nfound;
1020
1021 return 0;
1022 }
1023
1024 /* meant to use as a return value, this macro assumes the ints are different */
1025 /* (history lists are now long, so this is a speed-up) 19 May 2008 [rickr] */
1026 #define MY_INT_COMPARE(a,b) (((a)<(b)) ? -GD.sort_dir : GD.sort_dir )
1027
1028 /* sort by date, then by author, level and program */
compare_hlist(const void * v0,const void * v1)1029 int compare_hlist(const void *v0, const void *v1)
1030 {
1031 hist_type * h0 = *(hist_type **)v0;
1032 hist_type * h1 = *(hist_type **)v1;
1033 int rv = 0;
1034
1035 if( GD.sort_by_prog ) {
1036 if( h0->program && h1->program ) rv = strcmp(h0->program, h1->program);
1037 if( rv ) return MY_INT_COMPARE(rv, 0);
1038 }
1039
1040 if( h0->yyyy != h1->yyyy ) return MY_INT_COMPARE(h0->yyyy, h1->yyyy);
1041 if( h0->mm != h1->mm ) return MY_INT_COMPARE(h0->mm , h1->mm );
1042 if( h0->dd != h1->dd ) return MY_INT_COMPARE(h0->dd , h1->dd );
1043
1044 /* ignore any string that is not set (rather than return as equal) */
1045 /* 19 May 2008 [rickr] */
1046
1047 if( h0->author && h1->author ) rv = strcmp(h0->author, h1->author);
1048 if( rv ) return MY_INT_COMPARE(rv, 0);
1049
1050 if( h0->level != h1->level ) return MY_INT_COMPARE(h0->level, h1->level);
1051
1052 if( ! GD.sort_by_prog ) {
1053 if( h0->program && h1->program ) rv = strcmp(h0->program, h1->program);
1054 if( rv ) return MY_INT_COMPARE(rv, 0);
1055 }
1056
1057 return 0;
1058 }
1059
1060 /* compare only the dates -- RWC */
compare_hist_dates(hist_type * h0,hist_type * h1)1061 int compare_hist_dates(hist_type *h0 , hist_type *h1)
1062 {
1063 if( h0->yyyy != h1->yyyy ) return MY_INT_COMPARE(h0->yyyy, h1->yyyy);
1064 if( h0->mm != h1->mm ) return MY_INT_COMPARE(h0->mm , h1->mm );
1065 if( h0->dd != h1->dd ) return MY_INT_COMPARE(h0->dd , h1->dd );
1066 return 0 ;
1067 }
1068
1069 /* reallocate space, and fill the array of longer struct pointers
1070 * (note that 'hadd' is a an array of structures) */
add_to_hlist(hist_type *** hlist,hist_type * hadd,int addlen,int * newlen)1071 int add_to_hlist(hist_type *** hlist, hist_type * hadd,
1072 int addlen, int * newlen)
1073 {
1074 int c, prevlen;
1075
1076 if( GD.verb > 4 )
1077 fprintf(stderr,"++ appending len %d hist to len %d total\n",
1078 addlen, *newlen);
1079
1080 if( addlen <= 0 ) return 0; /* nothing to add */
1081
1082 prevlen = *newlen; /* store for starting index to adjust */
1083 *newlen += addlen;
1084 *hlist = (hist_type **)realloc(*hlist, *newlen*sizeof(hist_type *));
1085 if( !*hlist ) {
1086 fprintf(stderr,"** failed to realloc hlist ptrs of len %d\n", *newlen);
1087 return 1;
1088 }
1089
1090 for(c = 0; c < addlen; c++)
1091 (*hlist)[c+prevlen] = hadd + c; /* applies struct offsets */
1092
1093 if( GD.verb > 5 && addlen > 2 ) {
1094 fprintf(stderr,"++ first 3 new addresses are: %p, %p, %p\n",
1095 (*hlist)[prevlen], (*hlist)[prevlen+1], (*hlist)[prevlen+2]);
1096 fprintf(stderr," programs are %s, %s, %s\n",
1097 CHECK_NULL_STR((*hlist)[prevlen]->program),
1098 CHECK_NULL_STR((*hlist)[prevlen+1]->program),
1099 CHECK_NULL_STR((*hlist)[prevlen+2]->program));
1100 }
1101
1102 return 0;
1103 }
1104
1105
1106 /* reallocate space, and fill the array of struct pointers
1107 * (fill from first of length newlen) */
restrict_hlist(hist_type *** hlist,int first,int oldlen,int newlen)1108 int restrict_hlist(hist_type *** hlist, int first, int oldlen, int newlen)
1109 {
1110 hist_type ** horig = *hlist; /* store the current list */
1111 int c;
1112
1113 if( GD.verb > 2 )
1114 fprintf(stderr,"++ making new len %d (of %d) list at offset %d\n",
1115 newlen, oldlen, first);
1116
1117 if( newlen < 0 || first < 0 || oldlen < 0) {
1118 fprintf(stderr,"** restrict_hlist: bad newlen, first, old = %d, %d, %d\n",
1119 newlen, first, oldlen);
1120 return 1;
1121 } else if( newlen+first > oldlen ) {
1122 fprintf(stderr,"** restrict_hlist: newlen+first (%d+%d) > oldlen (%d)\n",
1123 newlen, first, oldlen);
1124 return 1;
1125 }
1126
1127 if( newlen == oldlen ) return 0; /* easy... */
1128
1129 *hlist = (hist_type **)malloc(newlen*sizeof(hist_type *));
1130 if( !*hlist ) {
1131 fprintf(stderr,"** RH: failed to malloc hlist ptrs of len %d\n", newlen);
1132 return 1;
1133 }
1134
1135 for(c = 0; c < newlen; c++)
1136 (*hlist)[c] = horig[first+c]; /* applies struct offsets */
1137
1138 if( GD.verb > 5 && newlen > 2 ) {
1139 fprintf(stderr,"++ RH: first 3 new addresses are: %p, %p, %p\n",
1140 (*hlist)[0], (*hlist)[1], (*hlist)[2]);
1141 fprintf(stderr," programs are %s, %s, %s\n",
1142 CHECK_NULL_STR((*hlist)[0]->program),
1143 CHECK_NULL_STR((*hlist)[1]->program),
1144 CHECK_NULL_STR((*hlist)[2]->program));
1145 }
1146
1147 free(horig); /* incoming list is garbage */
1148
1149 return 0;
1150 }
1151
1152
disp_global_data(char * mesg,global_data * gd)1153 int disp_global_data(char * mesg, global_data * gd)
1154 {
1155 if( mesg ) fputs(mesg, stderr);
1156
1157 if( !gd ) return 1;
1158
1159 fprintf(stderr,"global_data struct: \n"
1160 " author = %s\n"
1161 " program = %s\n"
1162 " check_date = %d\n"
1163 " cd_day, cd_month, cd_year = %s, %s, %s\n"
1164 " show_field = %d\n"
1165 " html, dline, type = %d, %d, %d\n"
1166 " level, min_level = %d, %d\n"
1167 " past_days, months, years = %d, %d, %d\n"
1168 " past_entries = %d\n"
1169 " sort_dir, final_by_prog = %d, %d\n"
1170 " verb, SBP, plen = %d, %d, %d\n",
1171 CHECK_NULL_STR(gd->author), CHECK_NULL_STR(gd->program),
1172 gd->check_date, CHECK_NULL_STR(gd->cd_day),
1173 CHECK_NULL_STR(gd->cd_month), CHECK_NULL_STR(gd->cd_year),
1174 gd->show_field, gd->html, gd->dline, gd->type,
1175 gd->level, gd->min_level,
1176 gd->past_days, gd->past_months, gd->past_years,
1177 gd->past_entries, gd->sort_dir, gd->final_sort_by_prog,
1178 gd->verb, gd->sort_by_prog, gd->plen);
1179
1180 return 0;
1181 }
1182
show_help(void)1183 int show_help(void)
1184 {
1185 printf(
1186 "afni_history: show AFNI updates per user, dates or levels\n"
1187 "\n"
1188 "This program is meant to display a log of updates to AFNI code, the\n"
1189 "website, educational material, etc. Users can specify a level of\n"
1190 "importance, the author, program or how recent the changes are.\n"
1191 "\n"
1192 "The levels of importance go from 1 to 4, with meanings:\n"
1193 " 1 - users would not care\n"
1194 " 2 - of little importance, though some users might care\n"
1195 " 3 - fairly important\n"
1196 " 4 - a big change or new program\n"
1197 " 5 - IMPORTANT: we expect users to know\n"
1198 "\n"
1199 "-----------------------------------------------------------------\n"
1200 "\n"
1201 "common examples:\n"
1202 "\n"
1203 " 0. get help\n"
1204 "\n"
1205 " a. afni_history -help\n"
1206 "\n"
1207 " 1. display all of the history, possibly subject to recent days/entries\n"
1208 "\n"
1209 " a. afni_history\n"
1210 " b. afni_history -past_days 5\n"
1211 " c. afni_history -past_months 6\n"
1212 " d. afni_history -past_entries 1\n"
1213 "\n"
1214 " 2. select a specific type, level or minimum level\n"
1215 "\n"
1216 " a. afni_history -level 2\n"
1217 " b. afni_history -min_level 3 -type BUG_FIX\n"
1218 " c. afni_history -type 1 -min_level 3 -past_years 1\n"
1219 "\n"
1220 " 3. select a specific author or program\n"
1221 "\n"
1222 " a. afni_history -author rickr\n"
1223 " b. afni_history -program afni_proc.py\n"
1224 "\n"
1225 " display the past year of updates for rickr, sorted by program name...\n"
1226 "\n"
1227 " c. afni_history -author rickr -past_days 365 -final_sort_by_prog\n"
1228 "\n"
1229 " 4. select level 3+ suma updates from ziad over the past year\n"
1230 "\n"
1231 " a. afni_history -author ziad -min_level 3 -program suma\n"
1232 "\n"
1233 " 5. generate a web-page, maybe from the past year at at a minimum level\n"
1234 "\n"
1235 " a. afni_history -html -reverse > afni_hist_all.html\n"
1236 " b. afni_history -html -reverse -min_level 2 > afni_hist_level2.html\n"
1237 " c. afni_history -html -reverse -min_level 3 > afni_hist_level3.html\n"
1238 " d. afni_history -html -reverse -min_level 4 > afni_hist_level4.html\n"
1239 "\n"
1240 " 5. verify that the distribution is new enough\n"
1241 "\n"
1242 " Compare the most recent history entry against the passed date. If\n"
1243 " there is a history entry as recent as the given date, it is current.\n"
1244 " Otherwise, the distribution is considered old.\n"
1245 "\n"
1246 " a. afni_history -check_date 1 1 2010\n"
1247 " b. afni_history -check_date 15 Mar 2050\n"
1248 "\n"
1249 " 6. show particular fields\n"
1250 "\n"
1251 " a. show the program name for the most recent 10 entries\n"
1252 " afni_history -show_field program 10\n"
1253 " b. show the program name for the most recent 10 NEW_PROG entries\n"
1254 " afni_history -type NEW_PROG -show_field program 10\n"
1255 " c. show the authors of the past 10 BUG_FIX entries\n"
1256 " afni_history -type BUG_FIX -show_field author 10\n"
1257 " d. show all unique program fields, sorted\n"
1258 " afni_history -show_field program | sort | uniq\n"
1259 " e. show all unique program fields of NEW_PROG entries, sorted\n"
1260 " afni_history -show_field program -type NEW_PROG | sort | uniq\n"
1261 "\n"
1262 " f. list FIELD parameters available for use with -show_field\n"
1263 " afni_history -show_field_names\n"
1264 "\n"
1265 "-----------------------------------------------------------------\n"
1266 "\n"
1267 "------------------ informational options: -----------------------\n"
1268 "\n"
1269 " -help : show this help\n"
1270 " -hist : show this program's history\n"
1271 " -list_authors : show the list of valid authors\n"
1272 " -list_types : show the list of valid change types\n"
1273 " -ver : show this program's version\n"
1274 "\n"
1275 "------------------ output restriction options: ------------------\n"
1276 "\n"
1277 " -author AUTHOR : restrict output to the given AUTHOR\n"
1278 " -level LEVEL : restrict output to the given LEVEL\n"
1279 " -min_level LEVEL : restrict output to at least level LEVEL\n"
1280 " -program PROGRAM : restrict output to the given PROGRAM\n"
1281 "\n"
1282 " -past_entries ENTRIES : restrict output to final ENTRIES entries\n"
1283 " -past_days DAYS : restrict output to the past DAYS days\n"
1284 " -past_months MONTHS : restrict output to the past MONTHS months\n"
1285 " -past_years YEARS : restrict output to the past YEARS years\n"
1286 "\n"
1287 " -type TYPE : restrict output to the given TYPE\n"
1288 " (TYPE = 0..5, or strings 'NEW_PROG', etc.)\n"
1289 " e.g. -type NEW_ENV\n"
1290 " e.g. -type BUG_FIX\n"
1291 "\n"
1292 "------------------ verification options: ------------------------\n"
1293 "\n"
1294 " -check_date DD MM YYYY : check history against given date\n"
1295 "\n"
1296 " If most recent afni_history is older than the passed date, the\n"
1297 " distribution version might be considered out of date. Otherwise, it\n"
1298 " might be considered current.\n"
1299 "\n"
1300 " If the version seems okay, afni_history returns 0, else 1.\n"
1301 " That way a script can check the status.\n"
1302 "\n"
1303 "------------------ general options: -----------------------------\n"
1304 "\n"
1305 " -html : add html formatting\n"
1306 " -dline : put a divider line between dates\n"
1307 " -reverse : reverse the sorting order\n"
1308 " (sort is by date, author, level, program)\n"
1309 "\n"
1310 " -show_field FIELD : restrict entry output to field FIELD\n"
1311 "\n"
1312 " For each history entry printed in the output restrict the output\n"
1313 " to only that implied by FIELD.\n"
1314 "\n"
1315 " Valid FIELDs include:\n"
1316 "\n"
1317 " FIELD : what to show\n"
1318 " ---------- ----------------------------------------\n"
1319 " all : all fields (same as without -show_field)\n"
1320 " firstline : only the standard first line\n"
1321 " day : day of month\n"
1322 " month : month of year\n"
1323 " year : year of calendar system\n"
1324 " date : year, month, day\n"
1325 " author : author\n"
1326 " program : program\n"
1327 " level : level\n"
1328 " type : type\n"
1329 " desc : description\n"
1330 " verbtext : verbtext\n"
1331 "\n"
1332 " Only one -field option is allowed at the moment.\n"
1333 "\n"
1334 " Valid FIELD values can be shown by\n"
1335 " afni_history -show_field_names\n"
1336 "\n"
1337 " -show_field_names : list valid FIELD names for -show_field\n"
1338 "\n"
1339 " Each entry in the output list can be passed to -show_field\n"
1340 "\n"
1341 " -verb LEVEL : request verbose output\n"
1342 " (LEVEL is from 0-6)\n"
1343 "\n"
1344 "\n"
1345 " Author: Rick Reynolds\n"
1346 " Thanks to: Ziad, Bob\n"
1347 "\n"
1348 );
1349
1350 return 0;
1351 }
1352
histlists_are_valid(histpair * hpairs,int plen)1353 int histlists_are_valid(histpair * hpairs, int plen)
1354 {
1355 int errs = 0, c;
1356
1357 if( GD.verb > 2 )
1358 fprintf(stderr,"-- checking for %d valid hlists...\n", plen);
1359
1360 for( c = 0; c < plen; c++ )
1361 if( ! valid_histlist(hpairs[c].hlist, hpairs[c].author) )
1362 errs++;
1363
1364 if( GD.verb > 2 ) fprintf(stderr,"++ number of bad hlists: %d\n", errs);
1365
1366 if( errs ) return 0;
1367 else return 1;
1368 }
1369
1370 /* print any error messages here */
valid_histlist(hist_type * hlist,char * author)1371 int valid_histlist(hist_type * hlist, char * author)
1372 {
1373 int c, len, errs;
1374
1375 if( !hlist || !author || !*author ) {
1376 fprintf(stderr,"** valid_histlist: invalid params\n");
1377 return 0;
1378 }
1379
1380 if( GD.verb > 2 ) {
1381 fprintf(stderr,"-- testing hlist for author ");
1382 fprintf(stderr,"%s ...\n", author);
1383 }
1384
1385 len = hlist_len(hlist);
1386 if( hlist_len <= 0 ) return 1;
1387
1388 errs = 0;
1389 for( c = 0; c < len; c++ )
1390 if( ! valid_histstruct(&hlist[c], author) ) {
1391 fprintf(stderr,"** bad hstruct[%d]\n", c);
1392 errs++;
1393 }
1394
1395 if( ! hlist_is_sorted(hlist) ) errs++;
1396
1397 if( errs )
1398 fprintf(stderr,"** author %s, found %d bad structs\n", author, errs);
1399 else if ( GD.verb > 1 )
1400 fprintf(stderr,"++ author %s, %d structs, OK\n\n", author, len);
1401
1402 if( errs ) return 0;
1403 else return 1;
1404 }
1405
1406 /* rcr - finish this, check that the dates are sorted (one way or the other) */
hlist_is_sorted(hist_type * hlist)1407 int hlist_is_sorted(hist_type * hlist)
1408 {
1409 return 1;
1410 }
1411
valid_histstruct(hist_type * hstr,char * author)1412 int valid_histstruct(hist_type * hstr, char * author)
1413 {
1414 int errs = 0;
1415
1416 if( !hstr || !author ) {
1417 if( GD.verb > 1 ) fprintf(stderr,"** VHS: bad params\n");
1418 return 0;
1419 }
1420
1421 if( ! INT_IN_RANGE(hstr->dd, 1, 31) ) {
1422 fprintf(stderr,"** invalid day of month: %d\n", hstr->dd);
1423 errs++;
1424 }
1425
1426 if( ! INT_IN_RANGE(hstr->mm, 1, 12) ) {
1427 fprintf(stderr,"** invalid day of month: %d\n", hstr->mm);
1428 errs++;
1429 }
1430
1431 if( ! INT_IN_RANGE(hstr->yyyy, FIRST_YEAR, 2050) ) {
1432 fprintf(stderr,"** invalid year: %d\n", hstr->yyyy);
1433 errs++;
1434 }
1435
1436 if( ! hstr->author || strcmp(author, hstr->author) ) {
1437 fprintf(stderr,"** author mis-match: %s != %s\n",
1438 author, CHECK_NULL_STR(hstr->author));
1439 errs++;
1440 }
1441
1442 if( ! valid_program(hstr->program) ) {
1443 fprintf(stderr,"** invalid program: %s\n",
1444 CHECK_NULL_STR(hstr->program));
1445 errs++;
1446 }
1447
1448 if( ! INT_IN_RANGE(hstr->level, MIN_PROG_LEVEL, MAX_PROG_LEVEL) ) {
1449 fprintf(stderr,"** invalid level: %d\n", hstr->level);
1450 errs++;
1451 }
1452
1453 if( ! INT_IN_RANGE(hstr->type, 0, MAX_TYPE_VAL) ) {
1454 fprintf(stderr,"** invalid type: %d\n", hstr->type);
1455 errs++;
1456 }
1457
1458 if( ! valid_dstring(hstr->desc, MAX_LINE_CHARS) ) {
1459 fprintf(stderr,"** invalid desc: %s\n", CHECK_NULL_STR(hstr->desc));
1460 errs++;
1461 }
1462
1463 if( ! valid_dstring(hstr->verbtext, MAX_LINE_CHARS) ) {
1464 fprintf(stderr,"** invalid verbtext: %s\n",
1465 CHECK_NULL_STR(hstr->verbtext));
1466 errs++;
1467 }
1468
1469 if( errs ) return 0;
1470 else return 1;
1471 }
1472
1473 /* max_line_chars is the most that can pass w/out a \n,
1474 * except for one more at the end
1475 */
valid_dstring(char * str,int max_line_len)1476 int valid_dstring(char * str, int max_line_len)
1477 {
1478 int c, past, len;
1479
1480 if( !str || !*str ) return 1; /* okay to be empty */
1481
1482 len = strlen(str);
1483
1484 for( c = 1, past = 1; c < len; c++ ) {
1485 if( str[c] == '\n' ) { past = 0; continue; }
1486
1487 if( past >= max_line_len) {
1488 if( past == max_line_len && !str[c+1] ) break; /* this is okay */
1489
1490 /* otherwise, whine and fail */
1491 fprintf(stderr,"** string has long lines (max should be %d)\n",
1492 max_line_len);
1493
1494 if( max_line_len > 40 )
1495 fprintf(stderr,"-- okay up until '%40s'\n", str+c-40);
1496
1497 return 0;
1498 }
1499 }
1500
1501 return 1;
1502 }
1503
1504 /* consider checking a valid list */
valid_program(char * prog)1505 int valid_program(char * prog)
1506 {
1507 if( ! prog ) return 0;
1508 else return 1;
1509 }
1510
hlist_len(hist_type * hlist)1511 int hlist_len(hist_type * hlist)
1512 {
1513 int len = 0;
1514
1515 if( !hlist ) return 0;
1516
1517 for( len = 0; hlist[len].dd != 99; len++ )
1518 ;
1519
1520 if( GD.verb > 1 ) fprintf(stderr,"-- hlist '%s' has %d entries\n",
1521 CHECK_NULL_STR(hlist->author), len);
1522
1523 return len;
1524 }
1525
level_string(int level)1526 char * level_string(int level)
1527 {
1528 switch( level ) {
1529 default: return "INVALID";
1530 case MICRO: return "MICRO";
1531 case MINOR: return "MINOR";
1532 case MAJOR: return "MAJOR";
1533 case SUPER: return "SUPER";
1534 case SUPERDUPER: return "SUPERDUPER";
1535 }
1536 }
1537
type_string(int type)1538 char * type_string(int type)
1539 {
1540 switch( type ) {
1541 default: return "INVALID";
1542 case TYPE_GENERAL: return "GENERAL";
1543 case TYPE_NEW_PROG: return "NEW_PROG";
1544 case TYPE_NEW_OPT: return "NEW_OPT";
1545 case TYPE_NEW_ENV: return "NEW_ENV";
1546 case TYPE_BUG_FIX: return "BUG_FIX";
1547 case TYPE_MODIFY: return "MODIFY";
1548 case TYPE_ENHANCE: return "ENHANCE";
1549 case TYPE_REMOVE: return "REMOVE";
1550 case TYPE_REINSTATE: return "REINSTATE";
1551 }
1552 }
1553
type_string2type(char * tstring)1554 int type_string2type(char * tstring)
1555 {
1556 if( !tstring || !*tstring ) {
1557 fprintf(stderr,"** missing type string\n");
1558 return TYPE_INVALID;
1559 }
1560
1561 if( ! strcmp("GENERAL", tstring) ) return TYPE_GENERAL;
1562 if( ! strcmp("NEW_PROG", tstring) ) return TYPE_NEW_PROG;
1563 if( ! strcmp("NEW_OPT", tstring) ) return TYPE_NEW_OPT;
1564 if( ! strcmp("NEW_ENV", tstring) ) return TYPE_NEW_ENV;
1565 if( ! strcmp("BUG_FIX", tstring) ) return TYPE_BUG_FIX;
1566 if( ! strcmp("MODIFY", tstring) ) return TYPE_MODIFY;
1567 if( ! strcmp("ENHANCE", tstring) ) return TYPE_ENHANCE;
1568 if( ! strcmp("REMOVE", tstring) ) return TYPE_REMOVE;
1569 if( ! strcmp("REINSTATE",tstring) ) return TYPE_REINSTATE;
1570
1571 return TYPE_INVALID;
1572 }
1573
show_author_list(void)1574 int show_author_list(void)
1575 {
1576 char ** alist = g_author_list;
1577 int len = sizeof(g_author_list)/sizeof(char *);
1578 int c;
1579
1580 printf("\nafni_history author list:\n\n");
1581 for( c = 0; c < len-2; c+= 3 )
1582 printf(" %-12s %-3s %s\n", alist[c], alist[c+1], alist[c+2]);
1583 putchar('\n');
1584
1585 return 0;
1586 }
1587
show_valid_types(void)1588 int show_valid_types(void)
1589 {
1590 int c;
1591
1592 printf("\nvalid history types:\n\n");
1593 for( c = 0; c <= MAX_TYPE_VAL; c++ )
1594 printf(" %2d : %s\n", c, type_string(c));
1595 putchar('\n');
1596
1597 return 0;
1598 }
1599
1600 /* return an index into g_show_fields, or -1 on error */
field_name2index(char * fname)1601 int field_name2index(char * fname)
1602 {
1603 int nfields = sizeof(g_show_fields) / sizeof(char *);
1604 int c;
1605
1606 if( ! fname ) return -1;
1607
1608 for( c = 0; c < nfields; c++ )
1609 if( !strcasecmp(fname, g_show_fields[c]) )
1610 return c;
1611
1612 return -1; /* failure */
1613 }
1614
show_valid_fields(void)1615 int show_valid_fields(void)
1616 {
1617 int nfields = sizeof(g_show_fields) / sizeof(char *);
1618 int c;
1619
1620 printf("\nvalid field names (%d):\n\n", nfields);
1621 for( c = 0; c < nfields; c++ )
1622 printf(" %s\n", g_show_fields[c]);
1623 putchar('\n');
1624
1625 (void)quick_field_test();
1626
1627 return 0;
1628 }
1629
1630 /* just test some of the g_show_fields list against the #define's
1631 *
1632 * return 0 on success, 1 on error */
quick_field_test(void)1633 int quick_field_test(void)
1634 {
1635 /* "firstline" is first, "verbtext" is (currently) last */
1636 if( strcmp(g_show_fields[FIELD_FIRST_LINE], "firstline" )
1637 || strcmp(g_show_fields[FIELD_ALL], "all" )
1638 || strcmp(g_show_fields[FIELD_DATE], "date" )
1639 || strcmp(g_show_fields[FIELD_PROGRAM], "program" )
1640 || strcmp(g_show_fields[FIELD_VERB], "verbtext" ) )
1641 {
1642 fprintf(stderr,"** quick_field_test failure of g_show_fields\n");
1643 return 1;
1644 }
1645
1646 return 0; /* no problem */
1647 }
1648
1649 /* return 1 on some problem, else fill histpairs and set len */
init_histlist(global_data * gd)1650 int init_histlist( global_data * gd )
1651 {
1652 histpair * plist;
1653 int c;
1654
1655 if( !gd ) {
1656 fprintf(stderr,"** init_histlist: bad param\n");
1657 return 1;
1658 }
1659
1660 if( gd->verb > 2 ) fprintf(stderr,"++ init histlist...\n");
1661
1662 gd->histpairs = g_histpairs; /* point to global list */
1663
1664 plist = gd->histpairs; /* for convenience */
1665 c = 0;
1666 plist[c].hlist = bpittman_history; plist[c++].author = BGP;
1667 plist[c].hlist = christip_history; plist[c++].author = PPC;
1668 plist[c].hlist = dglen_history; plist[c++].author = DRG;
1669 plist[c].hlist = discoraj_history; plist[c++].author = JKR;
1670 plist[c].hlist = gangc_history; plist[c++].author = GC;
1671 plist[c].hlist = laurenpd_history; plist[c++].author = PDL;
1672 plist[c].hlist = ptaylor_history; plist[c++].author = PT;
1673 plist[c].hlist = rickr_history; plist[c++].author = RCR;
1674 plist[c].hlist = rwcox_history; plist[c++].author = RWC;
1675 plist[c].hlist = ziad_history; plist[c++].author = ZSS;
1676 gd->plen = c;
1677
1678 if( gd->plen > NUM_HIST_USERS ) {
1679 fprintf(stderr,"** NUM_HIST_USERS too small for %d users\n",gd->plen);
1680 return 1;
1681 }
1682
1683 if( gd->verb > 1 )
1684 fprintf(stderr,"-- histlist initialized with %d lists\n", gd->plen);
1685
1686 if( gd->verb > 4 ) {
1687 fprintf(stderr, "-- most recent programs are:\n");
1688 for( c = 0; c < gd->plen; c++ )
1689 fprintf(stderr," author %s, program %s\n",
1690 CHECK_NULL_STR(plist[c].hlist->author),
1691 CHECK_NULL_STR(plist[c].hlist->program));
1692 }
1693
1694 return 0;
1695 }
1696
1697