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>