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