Annotation of gnutrition/main.c, revision 1.1

1.1     ! asm         1: // SPDX-License-Identifier: GPL-3.0-or-later
        !             2: /*
        !             3:  * $Id$
        !             4:  *
        !             5:  * main.c - Entry point for GNUtrition
        !             6:  *
        !             7:  * Copyright (C) 2026 Free Software Foundation, Inc.
        !             8:  *
        !             9:  * Author: Jason Self <jself@gnu.org>
        !            10:  *         Anton McClure <asm@gnu.org>
        !            11:  */
        !            12: 
        !            13: #ifdef HAVE_CONFIG_H
        !            14: #include <config.h>
        !            15: #endif
        !            16: 
        !            17: #include "budget.h"
        !            18: #include "db.h"
        !            19: #include "log.h"
        !            20: #include "ui.h"
        !            21: #include "i18n.h"
        !            22: 
        !            23: #include <errno.h>
        !            24: #include <getopt.h>
        !            25: #include <locale.h>
        !            26: #include <stdio.h>
        !            27: #include <stdlib.h>
        !            28: #include <string.h>
        !            29: #include <sys/stat.h>
        !            30: #include <time.h>
        !            31: 
        !            32: #define PROGRAM_NAME "gnutrition"
        !            33: 
        !            34: static const struct option long_options[] =
        !            35: {
        !            36:   {"help",       no_argument,       NULL, 'h'},
        !            37:   {"version",    no_argument,       NULL, 'V'},
        !            38:   {"search",     required_argument, NULL, 's'},
        !            39:   {"info",       required_argument, NULL, 'i'},
        !            40:   {"log",        required_argument, NULL, 'l'},
        !            41:   {"quantity",   required_argument, NULL, 'n'},
        !            42:   {"delete",     required_argument, NULL, 'x'},
        !            43:   {"edit",       required_argument, NULL, 'e'},
        !            44:   {"budget",     no_argument,       NULL, 'b'},
        !            45:   {"calories",   required_argument, NULL, 'c'},
        !            46:   {"age",        required_argument, NULL, 'a'},
        !            47:   {"height",     required_argument, NULL, 'H'},
        !            48:   {"weight",     required_argument, NULL, 'w'},
        !            49:   {"activity",   required_argument, NULL, 'A'},
        !            50:   {"gender",     required_argument, NULL, 'G'},
        !            51:   {"date",       required_argument, NULL, 'd'},
        !            52:   {"db",         required_argument, NULL, 'D'},
        !            53:   {"profile-db", required_argument, NULL, 'P'},
        !            54:   {NULL, 0, NULL, 0}
        !            55: };
        !            56: 
        !            57: static void
        !            58: print_version (void)
        !            59: {
        !            60:   printf (_("GNUtrition %s\n"), PACKAGE_VERSION);
        !            61:   printf (_("Copyright (C) 2026 Free Software Foundation, Inc.\n"));
        !            62:   printf (_("License GPLv3+: GNU GPL version 3 or later "
        !            63:           "<http://gnu.org/licenses/gpl.html>\n"));
        !            64:   printf (_("This is free software: you are free to change "
        !            65:           "and redistribute it.\n"));
        !            66:   printf (_("There is NO WARRANTY, to the extent permitted by law.\n"));
        !            67: }
        !            68: 
        !            69: static void
        !            70: print_help (void)
        !            71: {
        !            72:   printf (_("Usage: %s [OPTION]...\n"), PROGRAM_NAME);
        !            73:   printf (_("Track your daily food intake using the USDA Food Pattern "
        !            74:           "budget system.\n\n"));
        !            75:   printf (_("With no options, starts the interactive ncurses interface.\n\n"));
        !            76:   printf (_("Options:\n"));
        !            77:   printf (_("  -s, --search=QUERY   search for foods matching QUERY\n"));
        !            78:   printf (_("  -i, --info=CODE      show nutrient info for food CODE\n"));
        !            79:   printf (_("  -l, --log=CODE       log a food by its code\n"));
        !            80:   printf (_("  -n, --quantity=NUM   number of servings to log "
        !            81:           "(default: 1.0)\n"));
        !            82:   printf (_("  -x, --delete=ID      delete a food log entry by its ID\n"));
        !            83:   printf (_("  -e, --edit=ID        edit a food log entry (use with "
        !            84:           "-n and/or -d)\n"));
        !            85:   printf (_("  -b, --budget         show today's budget summary\n"));
        !            86:   printf (_("  -c, --calories=KCAL  set daily calorie target "
        !            87:           "(default: 2000)\n"));
        !            88:   printf (_("  -a, --age=YEARS      your age in years "
        !            89:           "(for calorie estimation)\n"));
        !            90:   printf (_("  -H, --height=CM      your height in centimeters\n"));
        !            91:   printf (_("  -w, --weight=KG      your weight in kilograms\n"));
        !            92:   printf (_("  -A, --activity=LEVEL activity level: sedentary, light,\n"));
        !            93:   printf (_("                         moderate, very-active, "
        !            94:           "or extra-active\n"));
        !            95:   printf (_("  -d, --date=DATE      date for log/budget "
        !            96:           "(default: today)\n"));
        !            97:   printf (_("  -D, --db=PATH        path to food database "
        !            98:           "(default: food.db)\n"));
        !            99:   printf (_("  -P, --profile-db=PATH\n"));
        !           100:   printf (_("                       path to profile/log database\n"));
        !           101:   printf (_("                         (default: "
        !           102:           "$XDG_DATA_HOME/gnutrition/log.db)\n"));
        !           103:   printf (_("  -h, --help           display this help and exit\n"));
        !           104:   printf (_("  -V, --version        output version information and exit\n"));
        !           105:   printf (_("\nWhen --age, --height, --weight, and --activity are all given,\n"));
        !           106:   printf (_("the calorie target is estimated using the Mifflin-St Jeor\n"));
        !           107:   printf (_("equation and saved to your profile for future sessions.\n"));
        !           108:   printf (_("Use --calories to override the computed estimate.\n"));
        !           109:   printf (_("\nThe calorie level determines your daily food-group budget\n"));
        !           110:   printf (_("using the USDA Healthy US-Style Eating Pattern table\n"));
        !           111:   printf (_("(Dietary Guidelines for Americans, 2020-2025, Appendix 3).\n"));
        !           112:   printf (_("Values are rounded to the nearest 200 kcal pattern level\n"));
        !           113:   printf (_("(range: 1000-3200).\n"));
        !           114:   printf (_("\nYour profile and food log are stored in a separate database\n"));
        !           115:   printf (_("at $XDG_DATA_HOME/gnutrition/log.db (the USDA food database\n"));
        !           116:   printf (_("is never modified).\n"));
        !           117:   printf (_("\nReport bugs to: bug-gnutrition@gnu.org\n"));
        !           118:   printf (_("GNUtrition home page: "
        !           119:           "<http://www.gnu.org/software/gnutrition/>\n"));
        !           120:   printf (_("General help using GNU software: "
        !           121:           "<http://www.gnu.org/gethelp/>\n"));
        !           122: }
        !           123: 
        !           124: /* Parse an activity-level name.  Returns -1 if NAME is not
        !           125:    recognized.  */
        !           126: static int
        !           127: parse_activity (const char *name)
        !           128: {
        !           129:   if (strcmp (name, "sedentary") == 0)
        !           130:     return ACTIVITY_SEDENTARY;
        !           131:   if (strcmp (name, "light") == 0)
        !           132:     return ACTIVITY_LIGHT;
        !           133:   if (strcmp (name, "moderate") == 0)
        !           134:     return ACTIVITY_MODERATE;
        !           135:   if (strcmp (name, "very-active") == 0)
        !           136:     return ACTIVITY_VERY_ACTIVE;
        !           137:   if (strcmp (name, "extra-active") == 0)
        !           138:     return ACTIVITY_EXTRA_ACTIVE;
        !           139:   return -1;
        !           140: }
        !           141: 
        !           142: /* Return today's date as a dynamically-allocated string.  The caller
        !           143:    must free the result.  Returns NULL on failure.  */
        !           144: static char *
        !           145: get_today (void)
        !           146: {
        !           147:   char *buf;
        !           148:   time_t now;
        !           149:   struct tm *tm;
        !           150: 
        !           151:   buf = malloc (11);
        !           152:   if (!buf)
        !           153:     {
        !           154:       fprintf (stderr, _("%s: memory exhausted\n"), PROGRAM_NAME);
        !           155:       return NULL;
        !           156:     }
        !           157:   now = time (NULL);
        !           158:   tm = localtime (&now);
        !           159:   strftime (buf, 11, "%Y-%m-%d", tm);
        !           160:   return buf;
        !           161: }
        !           162: 
        !           163: /* Normalize a date string to ISO 8601 format (YYYY-MM-DD).  Accepts
        !           164:    the locale's preferred date representation (%x).  Returns a
        !           165:    dynamically-allocated string on success, or NULL on failure.  */
        !           166: static char *
        !           167: normalize_date (const char *input)
        !           168: {
        !           169:   struct tm tm;
        !           170:   char *buf;
        !           171: 
        !           172:   buf = malloc (11);
        !           173:   if (!buf)
        !           174:     {
        !           175:       fprintf (stderr, _("%s: memory exhausted\n"), PROGRAM_NAME);
        !           176:       return NULL;
        !           177:     }
        !           178: 
        !           179:   memset (&tm, 0, sizeof tm);
        !           180:   if (strptime (input, "%x", &tm) != NULL)
        !           181:     {
        !           182:       if (strftime (buf, 11, "%Y-%m-%d", &tm) > 0)
        !           183:         return buf;
        !           184:     }
        !           185: 
        !           186:   free (buf);
        !           187:   return NULL;
        !           188: }
        !           189: 
        !           190: /* Format an ISO 8601 date (YYYY-MM-DD) for display using the
        !           191:    locale's preferred date representation.  Returns a pointer to a
        !           192:    static buffer; not reentrant.  */
        !           193: static const char *
        !           194: format_date (const char *iso_date)
        !           195: {
        !           196:   static char buf[64];
        !           197:   struct tm tm;
        !           198: 
        !           199:   memset (&tm, 0, sizeof tm);
        !           200:   if (sscanf (iso_date, "%d-%d-%d",
        !           201:               &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3)
        !           202:     return iso_date;
        !           203:   tm.tm_year -= 1900;
        !           204:   tm.tm_mon -= 1;
        !           205:   if (strftime (buf, sizeof buf, "%x", &tm) == 0)
        !           206:     return iso_date;
        !           207:   return buf;
        !           208: }
        !           209: 
        !           210: /* Get the path to the user's log database.  Uses
        !           211:    $XDG_DATA_HOME/gnutrition/log.db or ~/.local/share/gnutrition/log.db.
        !           212:    Returns a dynamically-allocated string.  */
        !           213: static char *
        !           214: get_log_path (void)
        !           215: {
        !           216:   const char *data_home;
        !           217:   char *path;
        !           218:   size_t len;
        !           219: 
        !           220:   data_home = getenv ("XDG_DATA_HOME");
        !           221:   if (data_home && data_home[0] != '\0')
        !           222:     {
        !           223:       len = strlen (data_home) + strlen ("/gnutrition/log.db") + 1;
        !           224:       path = malloc (len);
        !           225:       if (!path)
        !           226:         {
        !           227:           fprintf (stderr, _("%s: memory exhausted\n"), PROGRAM_NAME);
        !           228:           return NULL;
        !           229:         }
        !           230:       snprintf (path, len, "%s/gnutrition/log.db", data_home);
        !           231:     }
        !           232:   else
        !           233:     {
        !           234:       const char *home = getenv ("HOME");
        !           235:       if (!home)
        !           236:         {
        !           237:           fprintf (stderr, _("%s: HOME is not set\n"), PROGRAM_NAME);
        !           238:           return NULL;
        !           239:         }
        !           240:       len = strlen (home)
        !           241:             + strlen ("/.local/share/gnutrition/log.db") + 1;
        !           242:       path = malloc (len);
        !           243:       if (!path)
        !           244:         {
        !           245:           fprintf (stderr, _("%s: memory exhausted\n"), PROGRAM_NAME);
        !           246:           return NULL;
        !           247:         }
        !           248:       snprintf (path, len, "%s/.local/share/gnutrition/log.db", home);
        !           249:     }
        !           250: 
        !           251:   return path;
        !           252: }
        !           253: 
        !           254: /* Ensure the directory containing PATH exists.  */
        !           255: static int
        !           256: ensure_dir (const char *path)
        !           257: {
        !           258:   char *dir;
        !           259:   char *slash;
        !           260:   char *copy;
        !           261: 
        !           262:   copy = strdup (path);
        !           263:   if (!copy)
        !           264:     {
        !           265:       fprintf (stderr, _("%s: memory exhausted\n"), PROGRAM_NAME);
        !           266:       return -1;
        !           267:     }
        !           268: 
        !           269:   /* Find the last slash to get the directory part.  */
        !           270:   slash = strrchr (copy, '/');
        !           271:   if (slash)
        !           272:     {
        !           273:       *slash = '\0';
        !           274:       dir = copy;
        !           275: 
        !           276:       /* Build the directory path component by component.  */
        !           277:       {
        !           278:         char *p;
        !           279:         for (p = dir + 1; *p; p++)
        !           280:           {
        !           281:             if (*p == '/')
        !           282:               {
        !           283:                 *p = '\0';
        !           284:                 if (mkdir (dir, 0755) < 0 && errno != EEXIST)
        !           285:                   {
        !           286:                     fprintf (stderr, _("%s: cannot create directory '%s': %s\n"),
        !           287:                              PROGRAM_NAME, dir, strerror (errno));
        !           288:                     free (copy);
        !           289:                     return -1;
        !           290:                   }
        !           291:                 *p = '/';
        !           292:               }
        !           293:           }
        !           294:         if (mkdir (dir, 0755) < 0 && errno != EEXIST)
        !           295:           {
        !           296:             fprintf (stderr, _("%s: cannot create directory '%s': %s\n"),
        !           297:                      PROGRAM_NAME, dir, strerror (errno));
        !           298:             free (copy);
        !           299:             return -1;
        !           300:           }
        !           301:       }
        !           302:     }
        !           303: 
        !           304:   free (copy);
        !           305:   return 0;
        !           306: }
        !           307: 
        !           308: /* Perform a CLI food search.  */
        !           309: static int
        !           310: cmd_search (sqlite3 *db, const char *query)
        !           311: {
        !           312:   struct food_list results;
        !           313:   size_t i;
        !           314: 
        !           315:   if (db_search_foods (db, query, &results) < 0)
        !           316:     return 1;
        !           317: 
        !           318:   if (results.count == 0)
        !           319:     {
        !           320:       printf (_("No foods found matching '%s'.\n"), query);
        !           321:     }
        !           322:   else
        !           323:     {
        !           324:       printf ("%-10s %s\n", _("Code"), _("Description"));
        !           325:       printf ("%-10s %s\n", "----------",
        !           326:               "--------------------------------------------");
        !           327:       for (i = 0; i < results.count; i++)
        !           328:         printf ("%-10d %s\n", results.items[i].food_code,
        !           329:                 results.items[i].description);
        !           330:     }
        !           331: 
        !           332:   food_list_free (&results);
        !           333:   return 0;
        !           334: }
        !           335: 
        !           336: /* Show nutrient information for a food code, scaled by SERVINGS.  */
        !           337: static int
        !           338: cmd_info (sqlite3 *db, int food_code, double servings)
        !           339: {
        !           340:   struct nutrient_list nutrients;
        !           341:   struct fped_entry fped;
        !           342:   size_t i;
        !           343: 
        !           344:   if (db_get_nutrients (db, food_code, &nutrients) < 0)
        !           345:     return 1;
        !           346: 
        !           347:   if (nutrients.count == 0)
        !           348:     {
        !           349:       printf (_("No nutrient data found for food code %d.\n"), food_code);
        !           350:     }
        !           351:   else
        !           352:     {
        !           353:       printf (_("Nutrient information for food code %d:\n\n"), food_code);
        !           354:       printf ("%-40s %10s\n", _("Nutrient"), _("Value"));
        !           355:       printf ("%-40s %10s\n",
        !           356:               "----------------------------------------",
        !           357:               "----------");
        !           358:       for (i = 0; i < nutrients.count; i++)
        !           359:         printf ("%-40s %10.2f\n", nutrients.items[i].name,
        !           360:                 nutrients.items[i].value * servings);
        !           361:     }
        !           362:   nutrient_list_free (&nutrients);
        !           363: 
        !           364:   /* Also show FPED budget cost if available.  */
        !           365:   if (db_get_fped (db, food_code, &fped) == 0)
        !           366:     {
        !           367:       if (servings != 1.0)
        !           368:         printf (_("\nFood Pattern Equivalents (per 100g x %.1f):\n"),
        !           369:                 servings);
        !           370:       else
        !           371:         printf (_("\nFood Pattern Equivalents (per 100g):\n"));
        !           372:       printf (_("  Vegetables: %.2f cup-eq\n"), fped.vegetables * servings);
        !           373:       printf (_("  Fruits:     %.2f cup-eq\n"), fped.fruits * servings);
        !           374:       printf (_("  Grains:     %.2f oz-eq\n"), fped.grains * servings);
        !           375:       printf (_("  Dairy:      %.2f cup-eq\n"), fped.dairy * servings);
        !           376:       printf (_("  Protein:    %.2f oz-eq\n"), fped.protein * servings);
        !           377:       printf (_("  Oils:       %.2f g\n"), fped.oils * servings);
        !           378:     }
        !           379: 
        !           380:   return 0;
        !           381: }
        !           382: 
        !           383: /* Show today's budget summary using the food log.  */
        !           384: static int
        !           385: cmd_budget (sqlite3 *food_db, sqlite3 *log_db, const char *date,
        !           386:             int calories)
        !           387: {
        !           388:   struct daily_budget budget;
        !           389:   struct daily_budget consumed;
        !           390:   struct log_list entries;
        !           391:   size_t i;
        !           392: 
        !           393:   budget = budget_for_calories (calories);
        !           394:   memset (&consumed, 0, sizeof consumed);
        !           395: 
        !           396:   if (log_get_day (log_db, date, &entries) < 0)
        !           397:     return 1;
        !           398: 
        !           399:   printf (_("Food log for %s:\n"), format_date (date));
        !           400:   if (entries.count == 0)
        !           401:     {
        !           402:       printf (_("  (no entries)\n"));
        !           403:     }
        !           404:   else
        !           405:     {
        !           406:       for (i = 0; i < entries.count; i++)
        !           407:         {
        !           408:           struct fped_entry fped;
        !           409:           printf (_("  %d - %s (%.1f servings)\n"),
        !           410:                   entries.items[i].food_code,
        !           411:                   entries.items[i].description,
        !           412:                   entries.items[i].servings);
        !           413:           if (db_get_fped (food_db, entries.items[i].food_code,
        !           414:                            &fped) == 0)
        !           415:             {
        !           416:               double s = entries.items[i].servings;
        !           417:               consumed.vegetables += fped.vegetables * s;
        !           418:               consumed.fruits += fped.fruits * s;
        !           419:               consumed.grains += fped.grains * s;
        !           420:               consumed.dairy += fped.dairy * s;
        !           421:               consumed.protein += fped.protein * s;
        !           422:               consumed.oils += fped.oils * s;
        !           423:             }
        !           424:         }
        !           425:     }
        !           426: 
        !           427:   log_list_free (&entries);
        !           428: 
        !           429:   printf ("\n");
        !           430:   budget_print (&budget, &consumed);
        !           431:   return 0;
        !           432: }
        !           433: 
        !           434: static int
        !           435: parse_gender (const char *name)
        !           436: {
        !           437:   if (strcmp (name, "neutral") == 0)
        !           438:     return GENDER_NEUTRAL;
        !           439:   if (strcmp (name, "female") == 0)
        !           440:     return GENDER_FEMALE;
        !           441:   if (strcmp (name, "male") == 0)
        !           442:     return GENDER_MALE;
        !           443:   return -1;
        !           444: }
        !           445: 
        !           446: int
        !           447: main (int argc, char **argv)
        !           448: {
        !           449:   int c;
        !           450:   const char *db_path;
        !           451:   const char *search_query;
        !           452:   const char *date;
        !           453:   char *date_alloc;
        !           454:   char *log_path;
        !           455:   const char *log_path_explicit;
        !           456:   int info_code;
        !           457:   int log_code;
        !           458:   double log_servings;
        !           459:   int do_budget;
        !           460:   int calories;
        !           461:   int calories_explicit;  /* 1 if --calories was given  */
        !           462:   int profile_age;
        !           463:   double profile_height;
        !           464:   double profile_weight;
        !           465:   int profile_activity;
        !           466:   int profile_gender;
        !           467:   int profile_given;  /* bitmask: 1=age, 2=height, 4=weight, 8=activity  */
        !           468:   int mode;  /* 0 = interactive, 1 = search, 2 = info, 3 = log,
        !           469:                 4 = budget, 5 = delete, 6 = edit  */
        !           470:   int delete_id;
        !           471:   int edit_id;
        !           472:   int edit_quantity_given;
        !           473:   int edit_date_given;
        !           474:   sqlite3 *food_db;
        !           475:   sqlite3 *log_db;
        !           476:   int exit_status;
        !           477: 
        !           478:   /* Initialize variables explicitly (GNU Coding Standards).  */
        !           479:   setlocale (LC_ALL, "");
        !           480:   bindtextdomain ("gnutrition", LOCALEDIR);
        !           481:   textdomain ("gnutrition");
        !           482:   static char food_db_full_path[1024];
        !           483:   snprintf(food_db_full_path, sizeof(food_db_full_path), "%s/food.db", GNUTRITION_DATADIR);
        !           484:   db_path = food_db_full_path;
        !           485:   search_query = NULL;
        !           486:   date = NULL;
        !           487:   date_alloc = NULL;
        !           488:   log_path = NULL;
        !           489:   log_path_explicit = NULL;
        !           490:   info_code = 0;
        !           491:   log_code = 0;
        !           492:   log_servings = 1.0;
        !           493:   do_budget = 0;
        !           494:   calories = 2000;
        !           495:   calories_explicit = 0;
        !           496:   profile_age = 0;
        !           497:   profile_height = 0.0;
        !           498:   profile_weight = 0.0;
        !           499:   profile_activity = ACTIVITY_SEDENTARY;
        !           500:   profile_gender = GENDER_NEUTRAL;
        !           501:   profile_given = 0;
        !           502:   mode = 0;
        !           503:   delete_id = 0;
        !           504:   edit_id = 0;
        !           505:   edit_quantity_given = 0;
        !           506:   edit_date_given = 0;
        !           507:   food_db = NULL;
        !           508:   log_db = NULL;
        !           509:   exit_status = 0;
        !           510: 
        !           511:   while ((c = getopt_long (argc, argv, "hVs:i:l:n:x:e:bc:a:H:w:A:d:D:P:",
        !           512:                            long_options, NULL)) != -1)
        !           513:     {
        !           514:       switch (c)
        !           515:         {
        !           516:         case 'h':
        !           517:           print_help ();
        !           518:           return 0;
        !           519: 
        !           520:         case 'V':
        !           521:           print_version ();
        !           522:           return 0;
        !           523: 
        !           524:         case 's':
        !           525:           search_query = optarg;
        !           526:           mode = 1;
        !           527:           break;
        !           528: 
        !           529:         case 'i':
        !           530:           info_code = atoi (optarg);
        !           531:           mode = 2;
        !           532:           break;
        !           533: 
        !           534:         case 'l':
        !           535:           log_code = atoi (optarg);
        !           536:           mode = 3;
        !           537:           break;
        !           538: 
        !           539:         case 'n':
        !           540:           {
        !           541:             char *endp;
        !           542:             errno = 0;
        !           543:             log_servings = strtod (optarg, &endp);
        !           544:             if (errno != 0 || endp == optarg || *endp != '\0'
        !           545:                 || log_servings <= 0.0)
        !           546:               {
        !           547:                 fprintf (stderr,
        !           548:                          _("%s: invalid quantity '%s'\n"),
        !           549:                          PROGRAM_NAME, optarg);
        !           550:                 return 1;
        !           551:               }
        !           552:           }
        !           553:           edit_quantity_given = 1;
        !           554:           break;
        !           555: 
        !           556:         case 'x':
        !           557:           delete_id = atoi (optarg);
        !           558:           mode = 5;
        !           559:           break;
        !           560: 
        !           561:         case 'e':
        !           562:           edit_id = atoi (optarg);
        !           563:           mode = 6;
        !           564:           break;
        !           565: 
        !           566:         case 'b':
        !           567:           do_budget = 1;
        !           568:           mode = 4;
        !           569:           break;
        !           570: 
        !           571:         case 'c':
        !           572:           calories = budget_round_to_pattern (atoi (optarg));
        !           573:           calories_explicit = 1;
        !           574:           break;
        !           575: 
        !           576:         case 'a':
        !           577:           profile_age = atoi (optarg);
        !           578:           profile_given |= 1;
        !           579:           break;
        !           580: 
        !           581:         case 'H':
        !           582:           {
        !           583:             char *endp;
        !           584:             errno = 0;
        !           585:             profile_height = strtod (optarg, &endp);
        !           586:             if (errno != 0 || endp == optarg || *endp != '\0'
        !           587:                 || profile_height <= 0.0)
        !           588:               {
        !           589:                 fprintf (stderr,
        !           590:                          _("%s: invalid height '%s'\n"),
        !           591:                          PROGRAM_NAME, optarg);
        !           592:                 return 1;
        !           593:               }
        !           594:           }
        !           595:           profile_given |= 2;
        !           596:           break;
        !           597: 
        !           598:         case 'w':
        !           599:           {
        !           600:             char *endp;
        !           601:             errno = 0;
        !           602:             profile_weight = strtod (optarg, &endp);
        !           603:             if (errno != 0 || endp == optarg || *endp != '\0'
        !           604:                 || profile_weight <= 0.0)
        !           605:               {
        !           606:                 fprintf (stderr,
        !           607:                          _("%s: invalid weight '%s'\n"),
        !           608:                          PROGRAM_NAME, optarg);
        !           609:                 return 1;
        !           610:               }
        !           611:           }
        !           612:           profile_given |= 4;
        !           613:           break;
        !           614: 
        !           615:         case 'A':
        !           616:           profile_activity = parse_activity (optarg);
        !           617:           if (profile_activity < 0)
        !           618:             {
        !           619:               fprintf (stderr, _("%s: unknown activity level '%s'\n"),
        !           620:                        PROGRAM_NAME, optarg);
        !           621:               fprintf (stderr, _("Valid levels: sedentary, light, moderate, "
        !           622:                        "very-active, extra-active\n"));
        !           623:               return 1;
        !           624:             }
        !           625:           profile_given |= 8;
        !           626:           break;
        !           627: 
        !           628:         case 'G':
        !           629:           profile_gender = parse_gender (optarg);
        !           630:           if (profile_gender < 0)
        !           631:             {
        !           632:               fprintf (stderr, _("%s: unknown gender '%s'\n"),
        !           633:                        PROGRAM_NAME, optarg);
        !           634:               fprintf (stderr, _("Valid options: neutral, female, male\n"));
        !           635:               return 1;
        !           636:             }
        !           637:           break;
        !           638: 
        !           639:         case 'd':
        !           640:           date = optarg;
        !           641:           edit_date_given = 1;
        !           642:           break;
        !           643: 
        !           644:         case 'D':
        !           645:           db_path = optarg;
        !           646:           break;
        !           647: 
        !           648:         case 'P':
        !           649:           log_path_explicit = optarg;
        !           650:           break;
        !           651: 
        !           652:         default:
        !           653:           fprintf (stderr, _("Try '%s --help' for more information.\n"),
        !           654:                    PROGRAM_NAME);
        !           655:           return 1;
        !           656:         }
        !           657:     }
        !           658: 
        !           659:   /* Default date to today if not specified.  */
        !           660:   if (!date)
        !           661:     {
        !           662:       date_alloc = get_today ();
        !           663:       if (!date_alloc)
        !           664:         return 1;
        !           665:       date = date_alloc;
        !           666:     }
        !           667:   else
        !           668:     {
        !           669:       date_alloc = normalize_date (date);
        !           670:       if (!date_alloc)
        !           671:         {
        !           672:           fprintf (stderr, _("%s: invalid date: %s\n"), PROGRAM_NAME, date);
        !           673:           return 1;
        !           674:         }
        !           675:       date = date_alloc;
        !           676:     }
        !           677: 
        !           678:   /* Suppress unused variable warnings.  */
        !           679:   (void) do_budget;
        !           680: 
        !           681:   /* Open the food database.  */
        !           682:   food_db = db_open (db_path);
        !           683:   if (!food_db)
        !           684:     {
        !           685:       free (date_alloc);
        !           686:       return 1;
        !           687:     }
        !           688: 
        !           689:   /* Open the user's food log / profile database.  */
        !           690:   if (log_path_explicit)
        !           691:     {
        !           692:       log_path = strdup (log_path_explicit);
        !           693:       if (!log_path)
        !           694:         {
        !           695:           fprintf (stderr, _("%s: memory exhausted\n"), PROGRAM_NAME);
        !           696:           db_close (food_db);
        !           697:           free (date_alloc);
        !           698:           return 1;
        !           699:         }
        !           700:     }
        !           701:   else
        !           702:     {
        !           703:       log_path = get_log_path ();
        !           704:       if (!log_path)
        !           705:         {
        !           706:           db_close (food_db);
        !           707:           free (date_alloc);
        !           708:           return 1;
        !           709:         }
        !           710:     }
        !           711: 
        !           712:   if (ensure_dir (log_path) < 0)
        !           713:     {
        !           714:       db_close (food_db);
        !           715:       free (log_path);
        !           716:       free (date_alloc);
        !           717:       return 1;
        !           718:     }
        !           719: 
        !           720:   log_db = log_open (log_path);
        !           721:   if (!log_db)
        !           722:     {
        !           723:       db_close (food_db);
        !           724:       free (log_path);
        !           725:       free (date_alloc);
        !           726:       return 1;
        !           727:     }
        !           728: 
        !           729:   /* If all profile fields were given on the command line, compute the
        !           730:      calorie estimate, save the profile, and use the result (unless
        !           731:      --calories was also given, which takes precedence).  */
        !           732:   if (profile_given == 15)  /* all four bits set  */
        !           733:     {
        !           734:       struct user_profile prof;
        !           735:       prof.age_years = profile_age;
        !           736:       prof.height_cm = profile_height;
        !           737:       prof.weight_kg = profile_weight;
        !           738:       prof.activity_level = profile_activity;
        !           739:       prof.gender = profile_gender;
        !           740:       prof.calorie_target = budget_estimate_calories (profile_age,
        !           741:                                                       profile_height,
        !           742:                                                       profile_weight,
        !           743:                                                       profile_activity,
        !           744:                                                       profile_gender);
        !           745:       if (log_save_profile (log_db, &prof) < 0)
        !           746:         fprintf (stderr, _("%s: warning: could not save profile\n"),
        !           747:                  PROGRAM_NAME);
        !           748:       else
        !           749:         printf (_("Profile saved (estimated %d kcal/day).\n"),
        !           750:                 prof.calorie_target);
        !           751: 
        !           752:       if (!calories_explicit)
        !           753:         calories = prof.calorie_target;
        !           754:     }
        !           755:   else if (profile_given != 0)
        !           756:     {
        !           757:       fprintf (stderr, _("%s: --age, --height, --weight, and --activity "
        !           758:                "must all be given together\n"), PROGRAM_NAME);
        !           759:       log_close (log_db);
        !           760:       db_close (food_db);
        !           761:       free (log_path);
        !           762:       free (date_alloc);
        !           763:       return 1;
        !           764:     }
        !           765:   else if (!calories_explicit)
        !           766:     {
        !           767:       /* No profile args and no --calories: load saved profile.  */
        !           768:       struct user_profile saved;
        !           769:       int rc = log_get_profile (log_db, &saved);
        !           770:       if (rc == 0 && saved.calorie_target > 0)
        !           771:         calories = saved.calorie_target;
        !           772:     }
        !           773: 
        !           774:   switch (mode)
        !           775:     {
        !           776:     case 0:
        !           777:       /* Interactive ncurses mode.  */
        !           778:       exit_status = ui_run (food_db, log_db, calories) < 0 ? 1 : 0;
        !           779:       break;
        !           780: 
        !           781:     case 1:
        !           782:       exit_status = cmd_search (food_db, search_query);
        !           783:       break;
        !           784: 
        !           785:     case 2:
        !           786:       exit_status = cmd_info (food_db, info_code, log_servings);
        !           787:       break;
        !           788: 
        !           789:     case 3:
        !           790:       {
        !           791:         /* Log a food: first look up the description.  */
        !           792:         struct food_list results;
        !           793:         char code_str[32];
        !           794:         snprintf (code_str, sizeof code_str, "%d", log_code);
        !           795: 
        !           796:         /* Search by exact food code - we query for foods matching
        !           797:            the code string but we'll match by code.  */
        !           798:         if (db_search_foods (food_db, "", &results) == 0)
        !           799:           {
        !           800:             size_t j;
        !           801:             const char *desc = _("Unknown food");
        !           802:             for (j = 0; j < results.count; j++)
        !           803:               {
        !           804:                 if (results.items[j].food_code == log_code)
        !           805:                   {
        !           806:                     desc = results.items[j].description;
        !           807:                     break;
        !           808:                   }
        !           809:               }
        !           810:             if (log_add (log_db, log_code, desc, date,
        !           811:                         log_servings) < 0)
        !           812:               exit_status = 1;
        !           813:             else
        !           814:               printf (_("Logged food %d (%s) x%.1f for %s.\n"),
        !           815:                       log_code, desc, log_servings,
        !           816:                       format_date (date));
        !           817:             food_list_free (&results);
        !           818:           }
        !           819:         else
        !           820:           {
        !           821:             exit_status = 1;
        !           822:           }
        !           823:       }
        !           824:       break;
        !           825: 
        !           826:     case 4:
        !           827:       exit_status = cmd_budget (food_db, log_db, date, calories);
        !           828:       break;
        !           829: 
        !           830:     case 5:
        !           831:       /* Delete a log entry.  */
        !           832:       if (log_delete (log_db, delete_id) < 0)
        !           833:         exit_status = 1;
        !           834:       else
        !           835:         printf (_("Deleted log entry %d.\n"), delete_id);
        !           836:       break;
        !           837: 
        !           838:     case 6:
        !           839:       /* Edit a log entry.  At least -n or -d must be given.  */
        !           840:       if (!edit_quantity_given && !edit_date_given)
        !           841:         {
        !           842:           fprintf (stderr,
        !           843:                    _("%s: --edit requires --quantity and/or --date\n"),
        !           844:                    PROGRAM_NAME);
        !           845:           exit_status = 1;
        !           846:         }
        !           847:       else
        !           848:         {
        !           849:           /* We need to fetch the current entry to fill in unchanged
        !           850:              fields.  Use a simple query by iterating the date list
        !           851:              and entries.  We look up the entry by ID.  */
        !           852:           struct date_list dl;
        !           853:           int found = 0;
        !           854:           if (log_get_dates (log_db, &dl) == 0)
        !           855:             {
        !           856:               size_t di;
        !           857:               for (di = 0; di < dl.count && !found; di++)
        !           858:                 {
        !           859:                   struct log_list el;
        !           860:                   if (log_get_day (log_db, dl.dates[di], &el) == 0)
        !           861:                     {
        !           862:                       size_t ei;
        !           863:                       for (ei = 0; ei < el.count; ei++)
        !           864:                         {
        !           865:                           if (el.items[ei].id == edit_id)
        !           866:                             {
        !           867:                               double new_srv = edit_quantity_given
        !           868:                                 ? log_servings : el.items[ei].servings;
        !           869:                               const char *new_date = edit_date_given
        !           870:                                 ? date : el.items[ei].date;
        !           871:                               if (log_update (log_db, edit_id,
        !           872:                                               new_date, new_srv) < 0)
        !           873:                                 exit_status = 1;
        !           874:                               else
        !           875:                                 printf (_("Updated log entry %d "
        !           876:                                           "(%.1f servings, %s).\n"),
        !           877:                                         edit_id, new_srv,
        !           878:                                         format_date (new_date));
        !           879:                               found = 1;
        !           880:                               break;
        !           881:                             }
        !           882:                         }
        !           883:                       log_list_free (&el);
        !           884:                     }
        !           885:                 }
        !           886:               date_list_free (&dl);
        !           887:             }
        !           888:           if (!found && exit_status == 0)
        !           889:             {
        !           890:               fprintf (stderr,
        !           891:                        _("%s: log entry %d not found\n"),
        !           892:                        PROGRAM_NAME, edit_id);
        !           893:               exit_status = 1;
        !           894:             }
        !           895:         }
        !           896:       break;
        !           897: 
        !           898:     default:
        !           899:       abort ();
        !           900:     }
        !           901: 
        !           902:   log_close (log_db);
        !           903:   db_close (food_db);
        !           904:   free (log_path);
        !           905:   free (date_alloc);
        !           906:   return exit_status;
        !           907: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>