Compare commits

..

13 Commits

Author SHA1 Message Date
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
11 changed files with 236 additions and 25 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@ -97,7 +97,25 @@ $ menu-helper list
1 | Linguine Scampi | A lemony Italian pasta dish. 1 | Linguine Scampi | A lemony Italian pasta dish.
``` ```
### Modifying Recipe Ingredients/Tags ### 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 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: added erringly, you can correct this with the following commands:
@ -117,8 +135,8 @@ Scampi) that it is a pasta dish. We can do this with the following command:
```console ```console
$ menu-helper add-tag 1 pasta $ menu-helper add-tag 1 pasta
$ menu-helper info 1 $ menu-helper info 1
Name: Linguine Scampi Name: Linguine agli Scampi
Description: A lemony Italian pasta dish. Description: A zesty Italian pasta dish.
ID: 1 ID: 1
Ingredients: Ingredients:
@ -161,10 +179,10 @@ GitHub. However, you can save me a bit of work by just sending me the Git
patches directly (via E-Mail). patches directly (via E-Mail).
If you're looking for a way to contribute, consider having a look at my [To-Do If you're looking for a way to contribute, consider having a look at my [To-Do
list](/TODO.md) for the project. list](TODO.md) for the project.
## License ## License
This program is licensed under the terms & conditions of the GNU General Public 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. information.

18
TODO.md
View File

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

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

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

View File

@ -20,6 +20,8 @@
#include "util.hpp" #include "util.hpp"
#include <cstdlib> #include <cstdlib>
#include <sys/ioctl.h>
#include <iomanip>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <unistd.h> #include <unistd.h>
@ -71,6 +73,8 @@ int cmd_add(void) {
int cmd_list(int argc, char *argv[]) { int cmd_list(int argc, char *argv[]) {
db db; db db;
std::vector<std::string> ingredients, tags; std::vector<std::string> ingredients, tags;
struct winsize winsize;
const int id_col_sz = 5, name_col_sz = 24;
int opt; int opt;
while((opt = getopt(argc, argv, "i:t:")) not_eq -1) { while((opt = getopt(argc, argv, "i:t:")) not_eq -1) {
@ -92,10 +96,20 @@ int cmd_list(int argc, char *argv[]) {
} }
} }
ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
const int desc_col_sz = winsize.ws_col - (id_col_sz + name_col_sz + 1);
db.open(); db.open();
for(const auto &recipe : db.get_recipes(ingredients, tags)) std::cout << std::left << std::setw(id_col_sz) << "ID"
std::cout << recipe.id << " | " << recipe.name << " | " << recipe.description << std::endl; << 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;
}
db.close(); db.close();
@ -168,6 +182,48 @@ int cmd_info(const int id) {
return EXIT_SUCCESS; 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) { int cmd_add_ingr(const int recipe_id, const char *ingredients) {
db db; db db;
std::vector<std::string> ingr_list = split(ingredients, ","); std::vector<std::string> ingr_list = split(ingredients, ",");

View File

@ -21,6 +21,8 @@ int cmd_add(void);
int cmd_list(int argc, char *argv[]); int cmd_list(int argc, char *argv[]);
int cmd_delete(int argc, char *argv[]); int cmd_delete(int argc, char *argv[]);
int cmd_info(const int id); 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_add_ingr(const int recipe_id, const char *ingredients);
int cmd_rm_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_add_tag(const int recipe_id, const char *tags);

View File

@ -175,6 +175,26 @@ struct recipe db::get_recipe(const int id) {
return recipe; return recipe;
} }
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, std::vector<struct recipe> db::get_recipes(const std::vector<std::string> &ingredients,
const std::vector<std::string> &tags) const std::vector<std::string> &tags)
{ {

View File

@ -59,6 +59,8 @@ public:
} }
bool recipe_exists(const int id); bool recipe_exists(const int id);
struct recipe get_recipe(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, std::vector<struct recipe> get_recipes(const std::vector<std::string> &ingredients,
const std::vector<std::string> &tags); const std::vector<std::string> &tags);

View File

@ -39,33 +39,63 @@ int main(int argc, char *argv[]) {
try { try {
switch(id) { switch(id) {
case CMD_ADD: case CMD_ADD:
if(argc not_eq 2)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_add(); ret = cmd_add();
break; break;
case CMD_DEL: 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); ret = cmd_delete(argc - 2, argv + 2);
break; break;
case CMD_LIST: case CMD_LIST:
if(argc > 6)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
ret = cmd_list(argc - 1, argv + 1); ret = cmd_list(argc - 1, argv + 1);
break; break;
case CMD_INFO: 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])); ret = cmd_info(std::stoi(argv[2]));
break; 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: 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]); ret = cmd_add_ingr(std::stoi(argv[2]), argv[3]);
break; break;
case CMD_RM_INGR: 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]); ret = cmd_rm_ingr(std::stoi(argv[2]), argv[3]);
break; break;
case CMD_ADD_TAG: 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]); ret = cmd_add_tag(std::stoi(argv[2]), argv[3]);
break; break;
case CMD_RM_TAG: 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]); ret = cmd_rm_tag(std::stoi(argv[2]), argv[3]);
break; break;
case CMD_HELP: case CMD_HELP:
if(argc not_eq 2)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
print_help(); print_help();
break; break;
case CMD_VERSION: case CMD_VERSION:
if(argc not_eq 2)
throw "Invalid number of arguments. Use 'help' subcommand for more information.";
print_version(); print_version();
break; break;
default: default: