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