Compare commits

..

35 Commits

Author SHA1 Message Date
ccccacd88d Makefile: Create install directories.
Sometimes these directories may not exist.
2024-11-04 16:52:57 +01:00
3157d98ea1 Update To-Do list. 2024-11-04 16:39:50 +01:00
3bb7f52823 Bump to version v1.0 2024-11-04 16:37:59 +01:00
93da965805 Add message for man page to help information. 2024-11-04 16:37:30 +01:00
4d07cdf4d4 Makefile: Add man file to distclean target. 2024-11-04 16:35:48 +01:00
edb5d29040 Add man page. 2024-11-02 11:44:50 +01:00
61136eced5 'Uncheck' v1.0
It didn't do what I thought it would do.
2024-11-01 15:56:39 +01:00
c6c3b45ec9 Use TODO for version roadmap. 2024-11-01 15:55:44 +01:00
6158aaf673 Fix README example. 2024-10-29 19:46:06 +01:00
49bb2f4fc8 Properly align columns from list subcommand. 2024-10-29 19:44:09 +01:00
4379b0311e Add safeguards to avoid bad usage. 2024-10-29 17:59:57 +01:00
a3b6471c13 Fix markdown paths for Gitea.
Apparently on Gitea it doesn't use `/` to mean project root like it does
on GitHub.
2024-10-21 18:49:58 +02:00
907f8a7b41 Add new TODO list item. 2024-10-21 18:45:00 +02:00
009e3f09bf Add feature to edit recipe description. 2024-10-21 18:44:38 +02:00
320346911c Add feature to edit name.
And documentation for editing the description.
2024-10-21 18:37:53 +02:00
0b348e8c10 Add usage information to README. 2024-10-21 14:58:48 +02:00
f68a8b45a5 Add new items to To-Do list. 2024-10-21 14:58:36 +02:00
feed35d8c8 Change database creation message. 2024-10-21 14:30:16 +02:00
db0b607546 Move position of 'list' subcommand.
It makes more sense in terms of ordering.
2024-10-21 14:22:13 +02:00
d8a140e913 Add Contributing section to README. 2024-10-21 14:17:34 +02:00
a1e3322576 Add To-Do list. 2024-10-21 14:11:06 +02:00
802f3bfcc0 Normalize use of const in db method parameters. 2024-10-21 14:05:26 +02:00
5873ceb627 Enable adding and removing tags from recipes. 2024-10-21 14:03:04 +02:00
5bcc598880 Enable adding and removing ingredients from recipes. 2024-10-21 13:54:07 +02:00
03b1250006 Add unique constraint in relational tables. 2024-10-12 11:02:56 +02:00
c71467804e Fix SQL constraint NOT NULL.
I had used sed to change everything at once, so naturally this forwent
my notice.
2024-10-12 10:57:47 +02:00
c31ccdece2 Close database upon errors. 2024-10-12 10:48:35 +02:00
d2f30dcca7 Rename db functions s/db_// 2024-10-12 10:41:35 +02:00
2434a516cd Remove unused ret variable in cmd_info(). 2024-10-12 10:27:59 +02:00
d30f8df5c1 Put database in class and use throw exceptions. 2024-10-11 17:43:28 +02:00
ba7f930231 Rename functions command_* -> cmd_* 2024-10-11 16:18:22 +02:00
e92a578d65 Add "i" alias to info command. 2024-10-11 16:15:00 +02:00
1a590b477e Add info command. 2024-10-11 12:01:22 +02:00
c0c6959774 Regularize token use.
Mainly using the 'not' and 'not_eq' tokens.
2024-10-11 10:54:14 +02:00
08b4834d9e Remove old comments from porting. 2024-10-11 10:52:11 +02:00
11 changed files with 872 additions and 247 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/menu-helper
/compile_commands.json
.cache/*
/*.1.gz

View File

@ -21,6 +21,7 @@ DEFS=
CFLAGS=$(INCFLAGS) -std=c++20 -Wall -Wextra -Wfatal-errors -Werror
HDRS=src/util.hpp src/arg_parse.hpp src/db.hpp src/cmd.hpp
OBJS=src/main.o src/util.o src/arg_parse.o src/db.o src/cmd.o
DOCS=menu-helper.1
VERSION=1.0
ifeq ($(PREFIX),)
@ -39,13 +40,20 @@ endif
menu-helper: $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
menu-helper.1.gz: $(DOCS)
gzip -c $< > $@
.PHONY: clean distclean install
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) menu-helper.1.gz
$(RM) menu-helper
install: menu-helper
install: menu-helper menu-helper.1.gz
install -d $(PREFIX)/bin
install -m 755 menu-helper $(PREFIX)/bin/
install -d $(PREFIX)/share/man/man1
install -m 644 menu-helper.1.gz $(PREFIX)/share/man/man1/

172
README.md
View File

@ -1,11 +1,157 @@
# Menu Helper
# Menu-Helper
A program to manage a database of recipes and help you to pick out meals based
on filters of ingredients and tags.
## Usage
Ensure the `XDG_DATA_HOME` variable is set (e.g. to `$HOME/.local/share`).
Ensure the `XDG_DATA_HOME` variable is set (e.g. to `$HOME/.local/share`) and
that you have installed the SQLite3 library.
Upon first execution of any command, the program will automatically create the
database.
### Adding New Recipes
The first thing you're probably going to want to do is to add a new recipe to
your database. If this database hasn't been created already then the program
will do it automatically. This is done via the `add` subcommand, which will
query you about the different attributes you want for your recipe, looking
something like the following:
```console
$ menu-helper add
Name: Linguine Scampi
Description: A lemony Italian pasta dish.
Ingredients (comma separated): linguine,shrimp,garlic,parsley,lemon
Tags (comma separated): italian,lunch
Creating database in /home/nicolas/.local/share/menu-helper/recipes.db
```
This will have created your recipe within the database. That last line there is
merely informative, telling you that the database did not exist and it is not
being created; if you had a database already and it isn't being found, ensure
that your `XDG_DATA_HOME` environment variable is properly set.
### Querying Recipes
#### Filtering
Once a recipe or two have been added to your database you may now query them
filtering based on ingredients and tags. This is done via the `list` command,
which takes two kinds of arguments, both optional:
- `-i <list>`: Comma-separated list of the ingredients to look for.
- `-t <list>`: Comma-separated list of the tags to look for.
If neither is specified then all recipes will be listed with their respective
ID, name, and description:
```console
$ menu-helper list
1 | Linguine Scampi | A lemony Italian pasta dish.
2 | Garlic Soup | A simple monastic soup for cold winters.
```
However, when one of these arguments is used it filters recipes to only show
those which include __all__ the ingredients and tags specified:
```console
$ menu-helper list -i linguine
1 | Linguine Scampi | A lemony Italian pasta dish.
```
#### Recipe Information
The IDs shown in the queries above now become useful for the rest of
Menu-Helper functionality. If you wish to query all stored information about a
given recipe, this is where you can use the `info` subcommand with the relevant
ID:
```console
$ menu-helper info 2
Name: Garlic Soup
Description: A simple monastic soup for cold winters.
ID: 2
Ingredients:
- garlic
- bread
- egg
Tags:
- soup
- dinner
- simple
```
### Removing Recipes
If you end up desiring to remove a recipe for whatever reason, you can do so by
using the `del` subcommand with the recipe's corresponding ID:
```console
$ menu-helper del 2
$ menu-helper list
1 | Linguine Scampi | A lemony Italian pasta dish.
```
### Modifying Recipes
#### Name & Description
To correct or otherwise modify the name or description of your recipe, you can
use the `edit-name` and `edit-description` subcommands. These will prompt you
for the new name or description respectively and overwrite what was previously
stored in the database:
```console
$ menu-helper edit-name 1
New name: Linguine agli Scampi
$ menu-helper edit-description 1
New description: A zesty Italian pasta dish.
$ menu-helper list
1 | Linguine agli Scampi | A zesty Italian pasta dish.
```
#### Ingredients/Tags
If there are ingredients/tags which you forgot to add to a recipe, or that you
added erringly, you can correct this with the following commands:
- `add-ingr <id> <list>`: Add list of comma-separated ingredients `list` to
recipe with ID `id`.
- `rm-ingr <id> <list>`: Remove list of comma-separated ingredients `list` from
recipe with ID `id`.
- `add-tag <id> <list>`: Add list of comma-separated tags `list` to recipe with
ID `id`.
- `rm-tag <id> <list>`: Remove list of comma-separated tags `list` from recipe
with ID `id`.
For example, we forgot to add the useful tag to our first recipe (Linguine
Scampi) that it is a pasta dish. We can do this with the following command:
```console
$ menu-helper add-tag 1 pasta
$ menu-helper info 1
Name: Linguine agli Scampi
Description: A zesty Italian pasta dish.
ID: 1
Ingredients:
- linguine
- shrimp
- garlic
- parsley
- lemon
Tags:
- italian
- lunch
- pasta
```
## Building
@ -15,10 +161,28 @@ To build the program you will require the following dependencies:
- SQLite3 C/C++ library
- Make
Once installed, compile the project with the `make` command.
Once installed, compile the project with the `make` command. To install simply
run the `make install` command, optionally appending `PREFIX=...` to change the
default directory of installation (i.e. `/usr/local/...`).
## Contributing
If you find any issues, feel free to report them on GitHub or send me an E-Mail
(see my website/profile for the address). I will add these issues to my personal
Gitea page and (unless specified otherwise) mention you as the person who found
the issue.
For patches/pull requests, if you open a PR on GitHub I will likely not merge
directly but instead apply the patches locally (via Git patches, conserving
authorship), push them to my Gitea repository, which will finally be mirrored to
GitHub. However, you can save me a bit of work by just sending me the Git
patches directly (via E-Mail).
If you're looking for a way to contribute, consider having a look at my [To-Do
list](TODO.md) for the project.
## License
This program is licensed under the terms & conditions of the GNU General Public
License version 3 or greater. See the [LICENSE](/LICENSE) file for more
License version 3 or greater. See the [LICENSE](LICENSE) file for more
information.

16
TODO.md Normal file
View File

@ -0,0 +1,16 @@
# To-Do List
- [X] v1.0
- [X] Add basic functionality.
- [X] Add more safeguards to avoid bad usage.
- [X] Create a man page.
- [X] Add more documentation to `help` subcommand.
- [X] Properly align output columns from `list` subcommand.
- [X] Add feature for editing recipe name and description.
- [X] Name
- [X] Description
- [ ] v1.1
- [ ] Add import/export functionality.
- [ ] Allow for writing description in editor.
- [ ] Add examples to man page.

65
menu-helper.1 Normal file
View File

@ -0,0 +1,65 @@
.TH "MENU HELPER" "1" "November 2024" "menu-helper 1.0" "User Commands"
.SH "NAME"
menu-helper \- makes choosing meals easier
.SH "SYNOPSIS"
.B menu-helper
<\fICOMMAND\fR> [\fIOPTIONS\fR]
.SH "DESCRIPTION"
A program to manage a database of recipes and help you pick out meals based on
filters of ingredients and tags.
.SH "COMMANDS"
.TP
.B \fBadd\fR, \fBnew\fR
Add a new recipe to the database.
.TP
.B \fBdel\fR, \fBrm\fR <\fIid\fR>
Delete recipe with provided \fIid\fR.
.TP
.B \fBlist\fR, \fBls\fR [-i <\fIingredients\fR>] [-t <\fItags\fR>]
List all recipes that contain all \fIingredients\fR an \fItags\fR listed. If
none are listed, then it prints all recipes stored in the database. Both
\fIingredients\fR and \fItags\fR are comma-separated lists (e.g.
"garlic,tomato").
.TP
.B \fBinfo\fR <\fIid\fR>
Show all stored information on recipe with provided \fIid\fR.
.TP
.B \fBedit-name\fR <\fIid\fR>
Change the name of the recipe with the provided \fIid\fR.
.TP
.B \fBedit-description\fR, \fBedit-desc\fR <\fIid\fR>
Change the description of the recipe with the provided \fIid\fR.
.TP
.B \fBadd-ingr\fR <\fIid\fR> <\fIingredients\fR>
Add the specified \fIingredients\fR to the recipe with \fIid\fR, where
\fIingredients\fR is a comma-separated list (e.g. "garlic,tomato").
.TP
.B \fBrm-ingr\fR <\fIid\fR> <\fIingredients\fR>
Remove the specified \fIingredients\fR from the recipe with \fIid\fR, where
\fIingredients\fR is a comma-separated list (e.g. "garlic,tomato").
.TP
.B \fBadd-tag\fR <\fIid\fR> <\fItags\fR>
Add the specified \fItags\fR to the recipe with \fIid\fR, where \fItags\fR is a
comma-separated list (e.g. "dinner,simple").
.TP
.B \fBrm-tag\fR <\fIid\fR> <\fItags\fR>
Remove the specified \fItags\fR from the recipe with \fIid\fR, where \fItags\fR
is a comma-separated list (e.g. "dinner,simple").
.TP
.B \fBhelp\fR, \fB-h\fR, \fB--help\fR
Show basic help information.
.TP
.B \fBversion\fR, \fB-v\fR, \fB--version\fR
Show version information.
.SH "AUTHOR"
Written by Nicolás A. Ortega Froysa.
.SH "COPYRIGHT"
Copyright \(co 2024 Ortega Froysa, Nicolás A. <nicolas@ortegas.org>.
License: GNU General Public License version 3 or greater (see <https://gnu.org/licenses/gpl.html>).
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

View File

@ -25,16 +25,30 @@
enum cmd_id {
CMD_UNKNOWN = 0,
CMD_ADD,
CMD_LIST,
CMD_DEL,
CMD_LIST,
CMD_INFO,
CMD_EDIT_NAME,
CMD_EDIT_DESC,
CMD_ADD_INGR,
CMD_RM_INGR,
CMD_ADD_TAG,
CMD_RM_TAG,
CMD_HELP,
CMD_VERSION,
};
static const std::map<enum cmd_id, std::vector<std::string>> commands = {
{ CMD_ADD, {"add", "new"} },
{ CMD_LIST, {"list", "ls"} },
{ CMD_DEL, {"del", "rm"} },
{ CMD_LIST, {"list", "ls"} },
{ CMD_INFO, {"info", "i"} },
{ CMD_EDIT_NAME, {"edit-name"} },
{ CMD_EDIT_DESC, {"edit-description", "edit-desc"} },
{ CMD_ADD_INGR, {"add-ingr"} },
{ CMD_RM_INGR, {"rm-ingr"} },
{ CMD_ADD_TAG, {"add-tag"} },
{ CMD_RM_TAG, {"rm-tag"} },
{ CMD_HELP, {"help", "-h", "--help"} },
{ CMD_VERSION, {"version", "-v", "--version"} },
};
@ -52,12 +66,20 @@ static inline void print_help(void) {
print_usage();
std::cout << "COMMANDS:\n"
"\tadd, new Add a new recipe to the database.\n"
"\tlist, ls List recipes with filters.\n"
"\tdel, rm Delete recipe by ID.\n"
"\thelp, -h, --help Show this help information.\n"
"\tversion, -v, --version Show version information.\n"
"\tadd, new Add a new recipe to the database.\n"
"\tdel, rm Delete recipe by ID.\n"
"\tlist, ls List recipes with filters.\n"
"\tinfo Show recipe information.\n"
"\tedit-name Change recipe name.\n"
"\tedit-description, edit-desc Change recipe description.\n"
"\tadd-ingr Add ingredient to a recipe.\n"
"\trm-ingr Remove ingredient from a recipe.\n"
"\tadd-tag Add tag to a recipe.\n"
"\trm-tag Remove tag from a recipe.\n"
"\thelp, -h, --help Show this help information.\n"
"\tversion, -v, --version Show version information.\n"
<< std::endl;
std::cout << "For more information about subcommands, use 'man menu-helper'." << std::endl;
}
enum cmd_id parse_args(const std::string &cmd);

View File

@ -19,13 +19,16 @@
#include "db.hpp"
#include "util.hpp"
#include <iostream>
#include <cstdlib>
#include <sys/ioctl.h>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <vector>
int command_add(void) {
int cmd_add(void) {
db db;
std::string name, description, ingredients, tags;
int recipe_id, ingredient_id, tag_id;
@ -41,40 +44,40 @@ int command_add(void) {
std::cout << "Tags (comma separated): ";
getline(std::cin, tags);
if(not db_open()) {
std::cerr << "Failed to open database. Cannot add new entry." << std::endl;
return EXIT_FAILURE;
}
db.open();
if((recipe_id = db_get_recipe_id(name)) <= 0)
recipe_id = db_add_recipe(name, description);
if((recipe_id = db.get_recipe_id(name)) <= 0)
recipe_id = db.add_recipe(name, description);
for(auto &ingredient : split(ingredients, ",")) {
trim(ingredient);
if((ingredient_id = db_get_ingredient_id(ingredient)) <= 0)
ingredient_id = db_add_ingredient(ingredient);
db_conn_recipe_ingredient(recipe_id, ingredient_id);
if((ingredient_id = db.get_ingredient_id(ingredient)) <= 0)
ingredient_id = db.add_ingredient(ingredient);
db.conn_recipe_ingredient(recipe_id, ingredient_id);
}
for(auto &tag : split(tags, ",")) {
trim(tag);
if((tag_id = db_get_tag_id(tag)) <= 0)
tag_id = db_add_tag(tag);
db_conn_recipe_tag(recipe_id, tag_id);
if((tag_id = db.get_tag_id(tag)) <= 0)
tag_id = db.add_tag(tag);
db.conn_recipe_tag(recipe_id, tag_id);
}
db_close();
db.close();
return EXIT_SUCCESS;
}
int command_list(int argc, char *argv[]) {
int cmd_list(int argc, char *argv[]) {
db db;
std::vector<std::string> ingredients, tags;
struct winsize winsize;
const int id_col_sz = 5, name_col_sz = 24;
int opt;
while((opt = getopt(argc, argv, "i:t:")) != -1) {
while((opt = getopt(argc, argv, "i:t:")) not_eq -1) {
switch(opt) {
case 'i':
ingredients = split(optarg, ",");
@ -93,21 +96,28 @@ int command_list(int argc, char *argv[]) {
}
}
if(not db_open()) {
std::cerr << "Failed to open database. Cannot add new entry." << std::endl;
return EXIT_FAILURE;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
const int desc_col_sz = winsize.ws_col - (id_col_sz + name_col_sz + 1);
db.open();
std::cout << std::left << std::setw(id_col_sz) << "ID"
<< std::setw(name_col_sz) << "NAME"
<< std::setw(desc_col_sz) << "DESCRIPTION" << std::endl;
for(const auto &recipe : db.get_recipes(ingredients, tags)) {
std::cout << std::left << std::setw(id_col_sz) << recipe.id
<< std::setw(name_col_sz) << recipe.name
<< std::setw(desc_col_sz) << recipe.description << std::endl;
}
for(const auto &recipe : db_get_recipes(ingredients, tags))
std::cout << recipe.id << " | " << recipe.name << " | " << recipe.description << std::endl;
db_close();
db.close();
return EXIT_SUCCESS;
}
int command_delete(int argc, char *argv[]) {
int ret = EXIT_SUCCESS;
int cmd_delete(int argc, char *argv[]) {
db db;
std::vector<int> recipe_ids;
if(argc < 1) {
@ -115,25 +125,215 @@ int command_delete(int argc, char *argv[]) {
return EXIT_FAILURE;
}
if(not db_open()) {
std::cerr << "Failed to open database. Cannot add new entry." << std::endl;
return EXIT_FAILURE;
}
db.open();
for(int i = 0; i < argc; ++i) {
const int id = std::stoi(argv[i]);
if(not db_recipe_exists(id)) {
if(not db.recipe_exists(id)) {
std::cerr << "No recipe exists with ID " << id << "." << std::endl;
db.close();
return EXIT_FAILURE;
} else {
recipe_ids.push_back(id);
}
}
ret = (db_del_recipes(recipe_ids)) ? EXIT_SUCCESS : EXIT_FAILURE;
db.del_recipes(recipe_ids);
db.close();
db_close();
return ret;
return EXIT_SUCCESS;
}
int cmd_info(const int id) {
db db;
struct recipe recipe;
std::vector<std::string> ingredients, tags;
db.open();
if(not db.recipe_exists(id)) {
std::cerr << "No recipe with ID '" << id << "'";
db.close();
return EXIT_FAILURE;
}
recipe = db.get_recipe(id);
ingredients = db.get_recipe_ingredients(id);
tags = db.get_recipe_tags(id);
db.close();
std::cout << "Name: " << recipe.name << "\n"
<< "Description: " << recipe.description << "\n"
<< "ID: " << recipe.id << "\n"
<< std::endl;
std::cout << "Ingredients:" << std::endl;
for(auto &ingredient : ingredients)
std::cout << "\t- " << ingredient << std::endl;
std::cout << std::endl;
std::cout << "Tags:" << std::endl;
for(auto &tag : tags)
std::cout << "\t- " << tag << std::endl;
std::cout << std::endl;
return EXIT_SUCCESS;
}
int cmd_edit_name(const int id) {
db db;
std::string new_name;
db.open();
if(not db.recipe_exists(id)) {
std::cerr << "Recipe with ID " << id << " does not exist." << std::endl;
db.close();
return EXIT_FAILURE;
}
std::cout << "New name: ";
std::getline(std::cin, new_name);
db.update_recipe_name(id, new_name);
db.close();
return EXIT_SUCCESS;
}
int cmd_edit_desc(const int id) {
db db;
std::string new_desc;
db.open();
if(not db.recipe_exists(id)) {
std::cerr << "Recipe with ID " << id << " does not exist." << std::endl;
db.close();
return EXIT_FAILURE;
}
std::cout << "New name: ";
std::getline(std::cin, new_desc);
db.update_recipe_desc(id, new_desc);
db.close();
return EXIT_SUCCESS;
}
int cmd_add_ingr(const int recipe_id, const char *ingredients) {
db db;
std::vector<std::string> ingr_list = split(ingredients, ",");
db.open();
if(not db.recipe_exists(recipe_id)) {
std::cerr << "Recipe with ID " << recipe_id << " does not exist." << std::endl;
db.close();
return EXIT_FAILURE;
}
for(auto &i : ingr_list) {
int ingr_id;
trim(i);
if(not db.ingredient_exists(i))
ingr_id = db.add_ingredient(i);
else
ingr_id = db.get_ingredient_id(i);
db.conn_recipe_ingredient(recipe_id, ingr_id);
}
db.close();
return EXIT_SUCCESS;
}
int cmd_rm_ingr(const int recipe_id, const char *ingredients) {
db db;
std::vector<std::string> ingr_list = split(ingredients, ",");
db.open();
if(not db.recipe_exists(recipe_id)) {
std::cerr << "Recipe with ID " << recipe_id << " does not exist." << std::endl;
db.close();
return EXIT_FAILURE;
}
for(auto &i : ingr_list) {
int ingr_id;
trim(i);
if(not db.ingredient_exists(i)) {
std::cerr << "Could not find ingredient '" << i << "'. Skipping!" << std::endl;
continue;
}
ingr_id = db.get_ingredient_id(i);
db.disconn_recipe_ingredient(recipe_id, ingr_id);
}
db.close();
return EXIT_SUCCESS;
}
int cmd_add_tag(const int recipe_id, const char *tags) {
db db;
std::vector<std::string> tag_list = split(tags, ",");
db.open();
if(not db.recipe_exists(recipe_id)) {
std::cerr << "Recipe with ID " << recipe_id << " does not exist." << std::endl;
db.close();
return EXIT_FAILURE;
}
for(auto &i : tag_list) {
int tag_id;
trim(i);
if(not db.tag_exists(i))
tag_id = db.add_tag(i);
else
tag_id = db.get_ingredient_id(i);
db.conn_recipe_tag(recipe_id, tag_id);
}
db.close();
return EXIT_SUCCESS;
}
int cmd_rm_tag(const int recipe_id, const char *tags) {
db db;
std::vector<std::string> tag_list = split(tags, ",");
db.open();
if(not db.recipe_exists(recipe_id)) {
std::cerr << "Recipe with ID " << recipe_id << " does not exist." << std::endl;
db.close();
return EXIT_FAILURE;
}
for(auto &i : tag_list) {
int tag_id;
trim(i);
if(not db.tag_exists(i)) {
std::cerr << "Could not find tag '" << i << "'. Skipping!" << std::endl;
continue;
}
tag_id = db.get_tag_id(i);
db.disconn_recipe_tag(recipe_id, tag_id);
}
db.close();
return EXIT_SUCCESS;
}

View File

@ -17,6 +17,13 @@
*/
#pragma once
int command_add(void);
int command_list(int argc, char *argv[]);
int command_delete(int argc, char *argv[]);
int cmd_add(void);
int cmd_list(int argc, char *argv[]);
int cmd_delete(int argc, char *argv[]);
int cmd_info(const int id);
int cmd_edit_name(const int id);
int cmd_edit_desc(const int id);
int cmd_add_ingr(const int recipe_id, const char *ingredients);
int cmd_rm_ingr(const int recipe_id, const char *ingredients);
int cmd_add_tag(const int recipe_id, const char *tags);
int cmd_rm_tag(const int recipe_id, const char *tags);

View File

@ -17,28 +17,22 @@
*/
#include "db.hpp"
#include <sqlite3.h>
#include <cstdlib>
#include <iostream>
#include <filesystem>
#include <format>
#include <string>
#include <vector>
#include <iostream>
#include <sqlite3.h>
#include <stdexcept>
#define DB_VERSION 1
static sqlite3 *db = nullptr;
int db_open(void) {
void db::open(void) {
std::string xdg_data_home;
std::string db_path;
bool new_db = false;
int rc;
if((xdg_data_home = std::getenv("XDG_DATA_HOME")).empty()) {
std::cerr << "Cannot find environment variable XDG_DATA_HOME. Please define it before continuing. E.g.:\n"
"export XDG_DATA_HOME=\"$HOME/.local/share\"" << std::endl;
return 0;
}
if((xdg_data_home = std::getenv("XDG_DATA_HOME")).empty())
throw std::runtime_error("Cannot find environment variable XDG_DATA_HOME. Please define it before continuing.");
db_path = xdg_data_home + "/menu-helper";
@ -47,33 +41,31 @@ int db_open(void) {
db_path += "/recipes.db";
//if(access(db_path, F_OK) != 0) {
if(not std::filesystem::exists(db_path)) {
std::cout << "Creating database: " << db_path << std::endl;
std::cout << "Creating database in " << db_path << std::endl;
new_db = true;
}
rc = sqlite3_open(db_path.c_str(), &db);
if(sqlite3_open(db_path.c_str(), &sqlite_db) not_eq SQLITE_OK)
throw std::runtime_error("Failed to open database file " + db_path);
if(rc == SQLITE_OK && new_db) {
sqlite3_exec(db, "CREATE TABLE db_version(version INTEGER UNIQUE NOT nullptr);", nullptr, nullptr, nullptr);
//snprintf(insert_version_stmt, 64, "INSERT INTO db_version VALUES(%d);", DB_VERSION);
sqlite3_exec(db, std::format("INSERT INTO db_version VALUES({});", DB_VERSION).c_str(), nullptr, nullptr, nullptr);
sqlite3_exec(db, "CREATE TABLE tags(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE);", nullptr, nullptr, nullptr);
sqlite3_exec(db, "CREATE TABLE ingredients(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE);", nullptr, nullptr, nullptr);
sqlite3_exec(db, "CREATE TABLE recipes(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE, description STRING);", nullptr, nullptr, nullptr);
sqlite3_exec(db, "CREATE TABLE recipe_tag(recipe_id INTEGER REFERENCES recipes(id) ON DELETE CASCADE, tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE);", nullptr, nullptr, nullptr);
sqlite3_exec(db, "CREATE TABLE recipe_ingredient(recipe_id INTEGER REFERENCES recipes(id) ON DELETE CASCADE, ingredient_id INTEGER REFERENCES ingredients(id) ON DELETE CASCADE);", nullptr, nullptr, nullptr);
if(new_db) {
sqlite3_exec(sqlite_db, "CREATE TABLE db_version(version INTEGER UNIQUE NOT NULL);", nullptr, nullptr, nullptr);
sqlite3_exec(sqlite_db, std::format("INSERT INTO db_version VALUES({});", DB_VERSION).c_str(), nullptr, nullptr, nullptr);
sqlite3_exec(sqlite_db, "CREATE TABLE tags(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE);", nullptr, nullptr, nullptr);
sqlite3_exec(sqlite_db, "CREATE TABLE ingredients(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE);", nullptr, nullptr, nullptr);
sqlite3_exec(sqlite_db, "CREATE TABLE recipes(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE, description STRING);", nullptr, nullptr, nullptr);
sqlite3_exec(sqlite_db, "CREATE TABLE recipe_tag(recipe_id INTEGER REFERENCES recipes(id) ON DELETE CASCADE, tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE, UNIQUE(recipe_id, tag_id));", nullptr, nullptr, nullptr);
sqlite3_exec(sqlite_db, "CREATE TABLE recipe_ingredient(recipe_id INTEGER REFERENCES recipes(id) ON DELETE CASCADE, ingredient_id INTEGER REFERENCES ingredients(id) ON DELETE CASCADE, UNIQUE(recipe_id, ingredient_id));", nullptr, nullptr, nullptr);
}
return rc == SQLITE_OK;
}
void db_close(void) {
if(not db)
void db::close(void) {
if(not sqlite_db)
return;
sqlite3_close(db);
sqlite3_close(sqlite_db);
sqlite_db = nullptr;
}
int query_id_cb(void *recipe_id_var, int col_num, char **col_data, char **col_name) {
@ -91,43 +83,47 @@ int query_id_cb(void *recipe_id_var, int col_num, char **col_data, char **col_na
return ret;
}
int table_get_id_by_name(const std::string &table, const std::string &name) {
int db::table_get_id_by_name(const std::string &table, const std::string &name) {
int id = 0;
if(sqlite3_exec(db, std::format("SELECT id FROM {} WHERE lower(name)=lower('{}');", table, name).c_str(),
query_id_cb, &id, nullptr) != SQLITE_OK)
return -2;
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("SELECT id FROM {} WHERE lower(name)=lower('{}');", table, name).c_str(),
query_id_cb, &id, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to get ID of '{}' from table '{}'.", name, table));
}
return id;
}
int db_add_recipe(const std::string &name, const std::string &description) {
if(not db)
return -1;
int db::add_recipe(const std::string &name, const std::string &description) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(db, std::format("INSERT INTO recipes(name,description) VALUES('{}','{}');", name, description).c_str(),
nullptr, nullptr, nullptr) != SQLITE_OK)
return -2;
if(sqlite3_exec(sqlite_db, std::format("INSERT OR IGNORE INTO recipes(name,description) VALUES('{}','{}');", name, description).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error("Failed to insert new recipe into database.");
}
return db_get_recipe_id(name);
return get_recipe_id(name);
}
bool db_del_recipe(const int id) {
if(not db)
return false;
void db::del_recipe(const int id) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(db, std::format("DELETE FROM recipes WHERE id={}", id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK)
return false;
return true;
if(sqlite3_exec(sqlite_db, std::format("DELETE FROM recipes WHERE id={}", id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to delete recipe with ID {} from database.", id));
}
}
bool db_del_recipes(const std::vector<int> &ids) {
void db::del_recipes(const std::vector<int> &ids) {
std::string stmt = "DELETE FROM recipes WHERE id IN (";
if(not db)
return false;
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
bool first = true;
for(auto id : ids) {
@ -141,41 +137,74 @@ bool db_del_recipes(const std::vector<int> &ids) {
stmt += ");";
if(sqlite3_exec(db, stmt.c_str(), nullptr, nullptr, nullptr) not_eq SQLITE_OK)
return false;
return true;
if(sqlite3_exec(sqlite_db, stmt.c_str(), nullptr, nullptr, nullptr) not_eq SQLITE_OK)
throw std::runtime_error("Failed to delete recipes from database.");
}
bool db_recipe_exists(const int id) {
bool db::recipe_exists(const int id) {
bool exists = false;
if(not db)
return false;
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
sqlite3_exec(db, std::format("SELECT id FROM recipes WHERE id={}", id).c_str(),
[](void *found,int,char**,char**) {
*static_cast<bool*>(found) = true;
return 0;
}, &exists, nullptr);
if(sqlite3_exec(sqlite_db, std::format("SELECT id FROM recipes WHERE id={}", id).c_str(),
[](void *found,int,char**,char**) {
*static_cast<bool*>(found) = true;
return 0;
}, &exists, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error("Failed to select from database.");
}
return exists;
}
int db_get_recipe_id(const std::string &name) {
if(not db)
return -1;
struct recipe db::get_recipe(const int id) {
struct recipe recipe;
return table_get_id_by_name("recipes", name);
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("SELECT * FROM recipes WHERE id={};", id).c_str(),
[](void *recipe, int, char **col_data, char**) {
*static_cast<struct recipe*>(recipe) = { std::atoi(col_data[0]), col_data[1], col_data[2] };
return 0;
}, &recipe, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error("Failed to select from database.");
}
return recipe;
}
std::vector<struct recipe> db_get_recipes(const std::vector<std::string> &ingredients,
const std::vector<std::string> &tags)
void db::update_recipe_name(const int id, const std::string &new_name) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("UPDATE OR IGNORE recipes SET name='{}' WHERE id={};", new_name, id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to modify name of recipe with ID {}.", id));
}
}
void db::update_recipe_desc(const int id, const std::string &new_desc) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("UPDATE OR IGNORE recipes SET description='{}' WHERE id={};", new_desc, id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to modify description of recipe with ID {}.", id));
}
}
std::vector<struct recipe> db::get_recipes(const std::vector<std::string> &ingredients,
const std::vector<std::string> &tags)
{
std::vector<struct recipe> recipes;
std::string stmt = "SELECT id,name,description FROM recipes";
std::string filters;
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(not ingredients.empty() or not tags.empty())
filters += " WHERE";
@ -191,14 +220,10 @@ std::vector<struct recipe> db_get_recipes(const std::vector<std::string> &ingred
filters += " id IN (SELECT recipe_id FROM recipe_ingredient WHERE ingredient_id=";
// TODO: use throw?
if((id = db_get_ingredient_id(i)) < 0) {
std::cerr << "Failed to find ingredient '" << i << "'" << std::endl;
return std::vector<struct recipe>();
} else {
filters += std::to_string(id);
}
if((id = get_ingredient_id(i)) <= 0)
throw std::runtime_error(std::format("Failed to find ingredient '{}'", i));
filters += std::to_string(id);
filters += ")";
}
}
@ -218,87 +243,126 @@ std::vector<struct recipe> db_get_recipes(const std::vector<std::string> &ingred
filters += " id IN (SELECT recipe_id FROM recipe_tag WHERE tag_id=";
// TODO: use throw?
if((id = db_get_tag_id(i)) < 0) {
std::cerr << "Failed to find tag '" << i << "'" << std::endl;
return std::vector<struct recipe>();
} else {
filters += std::to_string(id);
}
if((id = get_tag_id(i)) <= 0)
throw std::runtime_error("Failed to find tag '{}'");
filters += std::to_string(id);
filters += ")";
}
}
stmt += filters + ";";
sqlite3_exec(db, stmt.c_str(),
[](void *recipe_list, int, char **col_data, char**) {
auto recipe_vec = static_cast<std::vector<struct recipe>*>(recipe_list);
recipe_vec->push_back({
std::atoi(col_data[0]),
col_data[1],
col_data[2] });
if(sqlite3_exec(sqlite_db, stmt.c_str(),
[](void *recipe_list, int, char **col_data, char**) {
static_cast<std::vector<struct recipe>*>(recipe_list)->push_back({
std::atoi(col_data[0]),
col_data[1],
col_data[2] });
return 0;
}, &recipes, nullptr);
}, &recipes, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error("Failed to select recipes.");
}
return recipes;
}
int db_add_ingredient(const std::string &name) {
if(not db)
return -1;
int db::add_ingredient(const std::string &name) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(db, std::format("INSERT INTO ingredients(name) VALUES(lower('{}'));", name).c_str(),
nullptr, nullptr, nullptr) != SQLITE_OK)
return -2;
if(sqlite3_exec(sqlite_db, std::format("INSERT OR IGNORE INTO ingredients(name) VALUES(lower('{}'));", name).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to instert ingredient '{}'.", name));
}
return db_get_ingredient_id(name);
return get_ingredient_id(name);
}
int db_get_ingredient_id(const std::string &name) {
if(not db)
return -1;
std::vector<std::string> db::get_recipe_ingredients(const int id) {
std::vector<std::string> ingredients;
return table_get_id_by_name("ingredients", name);
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("SELECT name FROM ingredients WHERE id IN (SELECT ingredient_id FROM recipe_ingredient WHERE recipe_id={});", id).c_str(),
[](void *ingredients, int, char **col_data, char**) {
static_cast<std::vector<std::string>*>(ingredients)->push_back(col_data[0]);
return 0;
}, &ingredients, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to select ingredients from recipe with ID {}", id));
}
return ingredients;
}
int db_add_tag(const std::string &name) {
if(not db)
return -1;
int db::add_tag(const std::string &name) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(db, std::format("INSERT INTO tags(name) VALUES('{}');", name).c_str(),
nullptr, nullptr, nullptr) != SQLITE_OK)
return -2;
if(sqlite3_exec(sqlite_db, std::format("INSERT OR IGNORE INTO tags(name) VALUES('{}');", name).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to insert tag '{}'", name));
}
return db_get_tag_id(name);
return get_tag_id(name);
}
int db_get_tag_id(const std::string &name) {
if(not db)
return -1;
std::vector<std::string> db::get_recipe_tags(const int id) {
std::vector<std::string> tags;
return table_get_id_by_name("tags", name);
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("SELECT name FROM tags WHERE id IN (SELECT tag_id FROM recipe_tag WHERE recipe_id={});", id).c_str(),
[](void *tags, int, char **col_data, char**) {
static_cast<std::vector<std::string>*>(tags)->push_back(col_data[0]);
return 0;
}, &tags, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to select tags for recipe with ID {}", id));
}
return tags;
}
int db_conn_recipe_ingredient(int recipe_id, int ingredient_id) {
if(not db)
return -1;
void db::conn_recipe_ingredient(const int recipe_id, const int ingredient_id) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(db, std::format("INSERT INTO recipe_ingredient(recipe_id, ingredient_id) VALUES({},{});", recipe_id, ingredient_id).c_str(),
nullptr, nullptr, nullptr) != SQLITE_OK)
return -2;
return 1;
if(sqlite3_exec(sqlite_db, std::format("INSERT OR IGNORE INTO recipe_ingredient(recipe_id, ingredient_id) VALUES({},{});", recipe_id, ingredient_id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to connect recipe with ID {} to ingredient with ID {}",
recipe_id, ingredient_id));
}
}
int db_conn_recipe_tag(int recipe_id, int tag_id) {
if(not db)
return -1;
void db::disconn_recipe_ingredient(const int recipe_id, const int ingredient_id) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(db, std::format("INSERT INTO recipe_tag(recipe_id, tag_id) VALUES({},{});", recipe_id, tag_id).c_str(),
nullptr, nullptr, nullptr) != SQLITE_OK)
return -2;
return 1;
if(sqlite3_exec(sqlite_db, std::format("DELETE FROM recipe_ingredient WHERE recipe_id={} AND ingredient_id={};", recipe_id, ingredient_id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to disconnect recipe with ID {} from ingredient with ID {}.", recipe_id, ingredient_id));
}
}
void db::conn_recipe_tag(const int recipe_id, const int tag_id) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("INSERT OR IGNORE INTO recipe_tag(recipe_id, tag_id) VALUES({},{});", recipe_id, tag_id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to connect recipe with ID {} to tag with ID {}",
recipe_id, tag_id));
}
}
void db::disconn_recipe_tag(const int recipe_id, const int tag_id) {
if(not sqlite_db)
throw std::runtime_error(std::format("{}: Database not open! Please contact a developer.", __PRETTY_FUNCTION__));
if(sqlite3_exec(sqlite_db, std::format("DELETE FROM recipe_tag WHERE recipe_id={} AND tag_id={};", recipe_id, tag_id).c_str(),
nullptr, nullptr, nullptr) not_eq SQLITE_OK) {
throw std::runtime_error(std::format("Failed to disconnect recipe with ID {} from tag with ID {}.", recipe_id, tag_id));
}
}

View File

@ -17,6 +17,7 @@
*/
#pragma once
#include <sqlite3.h>
#include <string>
#include <vector>
@ -26,53 +27,77 @@ struct recipe {
std::string description;
};
int db_open(void);
void db_close(void);
class db {
private:
sqlite3 *sqlite_db;
int table_get_id_by_name(const std::string &table, const std::string &name);
/**
* @brief Add a new recipe to the database.
*
* @param name Name of the new recipe.
* @param description Short description.
*
* @return ID of newly created recipe, -1 if DB isn't open, -2 on other failure.
*/
int db_add_recipe(const std::string &name, const std::string &description);
bool db_del_recipe(const int id);
bool db_del_recipes(const std::vector<int> &ids);
int db_get_recipe_id(const std::string &name);
bool db_recipe_exists(const int id);
static inline bool db_recipe_exists(const std::string &name) {
return (db_get_recipe_id(name) > 0);
}
std::vector<struct recipe> db_get_recipes(const std::vector<std::string> &ingredients,
const std::vector<std::string> &tags);
public:
db() : sqlite_db(nullptr) {}
~db() {
sqlite3_close(sqlite_db);
}
void open(void);
void close(void);
/**
* @brief Add a new ingredient to the database.
*
* @param name Name of the new ingredient.
*
* @return ID of newly created ingredient, -1 if DB isn't open, -2 on other failure.
*/
int db_add_ingredient(const std::string &name);
int db_get_ingredient_id(const std::string &name);
static inline bool db_ingredient_exists(const std::string &name) {
return (db_get_ingredient_id(name) > 0);
}
/**
* @brief Add a new recipe to the database.
*
* @param name Name of the new recipe.
* @param description Short description.
*
* @return ID of newly created recipe.
*/
int add_recipe(const std::string &name, const std::string &description);
void del_recipe(const int id);
void del_recipes(const std::vector<int> &ids);
inline int get_recipe_id(const std::string &name) {
return table_get_id_by_name("recipes", name);
}
inline bool recipe_exists(const std::string &name) {
return (get_recipe_id(name) > 0);
}
bool recipe_exists(const int id);
struct recipe get_recipe(const int id);
void update_recipe_name(const int id, const std::string &new_name);
void update_recipe_desc(const int id, const std::string &new_desc);
std::vector<struct recipe> get_recipes(const std::vector<std::string> &ingredients,
const std::vector<std::string> &tags);
/**
* @brief Add a new tag to the database.
*
* @param name Name of the new tag.
*
* @return ID of newly created tag, -1 if DB isn't open, -2 on other failure.
*/
int db_add_tag(const std::string &name);
int db_get_tag_id(const std::string &name);
static inline bool db_tag_exists(const std::string &name) {
return (db_get_tag_id(name) > 0);
}
/**
* @brief Add a new ingredient to the database.
*
* @param name Name of the new ingredient.
*
* @return ID of newly created ingredient.
*/
int add_ingredient(const std::string &name);
std::vector<std::string> get_recipe_ingredients(const int id);
inline int get_ingredient_id(const std::string &name) {
return table_get_id_by_name("ingredients", name);
}
inline bool ingredient_exists(const std::string &name) {
return (get_ingredient_id(name) > 0);
}
int db_conn_recipe_ingredient(int recipe_id, int ingredient_id);
int db_conn_recipe_tag(int recipe_id, int tag_id);
/**
* @brief Add a new tag to the database.
*
* @param name Name of the new tag.
*
* @return ID of newly created tag, -1 if DB isn't open, -2 on other failure.
*/
int add_tag(const std::string &name);
std::vector<std::string> get_recipe_tags(const int id);
inline int get_tag_id(const std::string &name) {
return table_get_id_by_name("tags", name);
}
inline bool tag_exists(const std::string &name) {
return (get_tag_id(name) > 0);
}
void conn_recipe_ingredient(const int recipe_id, const int ingredient_id);
void disconn_recipe_ingredient(const int recipe_id, const int ingredient_id);
void conn_recipe_tag(const int recipe_id, const int tag_id);
void disconn_recipe_tag(const int recipe_id, const int tag_id);
};

View File

@ -16,8 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <string>
#include "arg_parse.hpp"
#include "cmd.hpp"
@ -34,26 +36,77 @@ int main(int argc, char *argv[]) {
id = parse_args(argv[1]);
switch(id) {
case CMD_ADD:
ret = command_add();
break;
case CMD_LIST:
ret = command_list(argc - 1, argv + 1);
break;
case CMD_DEL:
ret = command_delete(argc - 2, argv + 2);
break;
case CMD_HELP:
print_help();
break;
case CMD_VERSION:
print_version();
break;
default:
std::cerr << "No such command '" << argv[1] << "'. Use 'help' sub-command." << std::endl;
print_usage();
return EXIT_FAILURE;
try {
switch(id) {
case CMD_ADD:
if(argc not_eq 2)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_add();
break;
case CMD_DEL:
if(argc not_eq 3)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_delete(argc - 2, argv + 2);
break;
case CMD_LIST:
if(argc > 6)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_list(argc - 1, argv + 1);
break;
case CMD_INFO:
if(argc not_eq 3)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_info(std::stoi(argv[2]));
break;
case CMD_EDIT_NAME:
if(argc not_eq 3)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_edit_name(std::stoi(argv[2]));
break;
case CMD_EDIT_DESC:
if(argc not_eq 3)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_edit_desc(std::stoi(argv[2]));
break;
case CMD_ADD_INGR:
if(argc not_eq 4)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_add_ingr(std::stoi(argv[2]), argv[3]);
break;
case CMD_RM_INGR:
if(argc not_eq 4)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_rm_ingr(std::stoi(argv[2]), argv[3]);
break;
case CMD_ADD_TAG:
if(argc not_eq 4)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_add_tag(std::stoi(argv[2]), argv[3]);
break;
case CMD_RM_TAG:
if(argc not_eq 4)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_rm_tag(std::stoi(argv[2]), argv[3]);
break;
case CMD_HELP:
if(argc not_eq 2)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
print_help();
break;
case CMD_VERSION:
if(argc not_eq 2)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
print_version();
break;
default:
std::cerr << "No such command '" << argv[1] << "'. Use 'help' sub-command." << std::endl;
print_usage();
ret = EXIT_FAILURE;
break;
}
} catch(const std::exception &e) {
std::cerr << e.what() << std::endl;
ret = EXIT_FAILURE;
}
return ret;