diff --git a/Makefile b/Makefile index cfbc155..d8c41b5 100644 --- a/Makefile +++ b/Makefile @@ -18,9 +18,9 @@ DEBUG=0 INCFLAGS= LDFLAGS=-lsqlite3 DEFS= -CFLAGS=$(INCFLAGS) -std=c++11 -Wall -Wextra -Wfatal-errors -Werror -HDRS=src/arg_parse.hpp src/db.hpp src/cmd.hpp -OBJS=src/main.o src/arg_parse.o src/db.o src/cmd.o +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 VERSION=1.0 ifeq ($(PREFIX),) diff --git a/src/cmd.cpp b/src/cmd.cpp index d3c7ec5..af22e47 100644 --- a/src/cmd.cpp +++ b/src/cmd.cpp @@ -17,84 +17,53 @@ */ #include "cmd.hpp" #include "db.hpp" +#include "util.hpp" -#include -#include -#include -#include +#include +#include +#include int command_add(void) { - char *name = NULL, *description = NULL, *ingredients = NULL, *tags = NULL; - size_t name_len, description_len, ingredients_len, tags_len; + std::string name, description, ingredients, tags; int recipe_id, ingredient_id, tag_id; - if(!db_open()) { - fprintf(stderr, "Failed to open database. Cannot add new entry.\n"); - return 0; + if(not db_open()) { + std::cerr << "Failed to open database. Cannot add new entry." << std::endl; + return EXIT_FAILURE; } - printf("Name: "); - getline(&name, &name_len, stdin); - // eliminate trailing newline - name[strlen(name) - 1] = '\0'; + std::cout << "Name: "; + getline(std::cin, name); - printf("Description: "); - getline(&description, &description_len, stdin); - // eliminate trailing newline - description[strlen(description) - 1] = '\0'; + std::cout << "Description: "; + getline(std::cin, description); - printf("Ingredients (comma separated): "); - getline(&ingredients, &ingredients_len, stdin); - // eliminate trailing newline - ingredients[strlen(ingredients) - 1] = '\0'; + std::cout << "Ingredients (comma separated): "; + getline(std::cin, ingredients); - printf("Tags (comma separated): "); - getline(&tags, &tags_len, stdin); - // eliminate trailing newline - tags[strlen(tags) - 1] = '\0'; + std::cout << "Tags (comma separated): "; + getline(std::cin, tags); if((recipe_id = db_get_recipe_id(name)) <= 0) recipe_id = db_add_recipe(name, description); - free(name); - free(description); - for(char *i = strtok(ingredients, ","); i; i = strtok(NULL,",")) { - // remove leading blank spaces - while(isblank(i[0])) - i += sizeof(char); + for(auto &ingredient : split(ingredients, ",")) { + trim(ingredient); - // remove trailing blank spaces - size_t i_len = strlen(i); - while(isblank(i[i_len - 1])) { - i[i_len - 1] = '\0'; - --i_len; - } - - if((ingredient_id = db_get_ingredient_id(i)) <= 0) - ingredient_id = db_add_ingredient(i); + if((ingredient_id = db_get_ingredient_id(ingredient)) <= 0) + ingredient_id = db_add_ingredient(ingredient); db_conn_recipe_ingredient(recipe_id, ingredient_id); } - free(ingredients); - for(char *i = strtok(tags, ","); i; i = strtok(NULL, ",")) { - // remove leading blank spaces - while(isblank(i[0])) - i += sizeof(char); + for(auto &tag : split(tags, ",")) { + trim(tag); - // remove trailing blank spaces - size_t i_len = strlen(i); - while(isblank(i[i_len - 1])) { - i[i_len - 1] = '\0'; - --i_len; - } - - if((tag_id = db_get_tag_id(i)) <= 0) - tag_id = db_add_tag(i); + if((tag_id = db_get_tag_id(tag)) <= 0) + tag_id = db_add_tag(tag); db_conn_recipe_tag(recipe_id, tag_id); } - free(tags); db_close(); - return 1; + return EXIT_SUCCESS; } diff --git a/src/db.cpp b/src/db.cpp index 5de802e..bb4c410 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -18,48 +18,46 @@ #include "db.hpp" #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#define DB_VERSION 1 static sqlite3 *db = NULL; -static const int db_version = 1; int db_open(void) { - const char *xdg_data_home; - char *db_path; - int new_db = 0; + std::string xdg_data_home; + std::string db_path; + bool new_db = false; int rc; - if(!(xdg_data_home = getenv("XDG_DATA_HOME"))) { - printf("Cannot find environment variable XDG_DATA_HOME. Please define it before continuing. E.g.:\nexport XDG_DATA_HOME=\"$HOME/.local/share\"\n"); + 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; } - db_path = malloc(strlen(xdg_data_home) + strlen("/menu-helper") + strlen("/recipes.db") + 1); - strcpy(db_path, xdg_data_home); - strcat(db_path, "/menu-helper"); + db_path = xdg_data_home + "/menu-helper"; - if(access(db_path, F_OK) != 0) - mkdir(db_path, 0700); + if(not std::filesystem::exists(db_path)) + std::filesystem::create_directories(db_path); - strcat(db_path, "/recipes.db"); + db_path += "/recipes.db"; - if(access(db_path, F_OK) != 0) { - printf("Creating database: %s\n", db_path); - new_db = 1; + //if(access(db_path, F_OK) != 0) { + if(not std::filesystem::exists(db_path)) { + std::cout << "Creating database: " << db_path << std::endl; + new_db = true; } - rc = sqlite3_open(db_path, &db); - free(db_path); + rc = sqlite3_open(db_path.c_str(), &db); if(rc == SQLITE_OK && new_db) { - char insert_version_stmt[64]; sqlite3_exec(db, "CREATE TABLE db_version(version INTEGER UNIQUE NOT NULL);", NULL, NULL, NULL); - snprintf(insert_version_stmt, 64, "INSERT INTO db_version VALUES(%d);", db_version); - sqlite3_exec(db, insert_version_stmt, NULL, NULL, NULL); + //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(), NULL, NULL, NULL); sqlite3_exec(db, "CREATE TABLE tags(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE);", NULL, NULL, NULL); sqlite3_exec(db, "CREATE TABLE ingredients(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE);", NULL, NULL, NULL); sqlite3_exec(db, "CREATE TABLE recipes(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING UNIQUE, description STRING);", NULL, NULL, NULL); @@ -71,7 +69,7 @@ int db_open(void) { } void db_close(void) { - if(!db) + if(not db) return; sqlite3_close(db); @@ -82,8 +80,8 @@ int query_id_cb(void *recipe_id_var, int col_num, char **col_data, char **col_na int ret = 1; for(int i = 0; i < col_num; ++i) { - if(strcmp(col_name[i], "id") == 0) { - *recipe_id_ptr = atoi(col_data[i]); + if(std::string(col_name[i]) == "id") { + *recipe_id_ptr = std::atoi(col_data[i]); ret = 0; break; } @@ -92,130 +90,88 @@ 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 char *table, const char *name) { - const char *sel_query_fmt = "SELECT id FROM %s WHERE lower(name)=lower('%s');"; - char *sel_query; +int table_get_id_by_name(const std::string &table, const std::string &name) { int id = 0; - sel_query = malloc(strlen(table) + strlen(name) + strlen(sel_query_fmt) + 1); - sprintf(sel_query, sel_query_fmt, table, name); - if(sqlite3_exec(db, sel_query, query_id_cb, &id, NULL) != SQLITE_OK) { - free(sel_query); + if(sqlite3_exec(db, std::format("SELECT id FROM {} WHERE lower(name)=lower('{}');", table, name).c_str(), + query_id_cb, &id, NULL) != SQLITE_OK) return -2; - } - - free(sel_query); return id; } -int db_add_recipe(const char *name, const char *description) { - const char *add_query_fmt = "INSERT INTO recipes(name,description) VALUES('%s','%s');"; - char *add_query; - - if(!db) +int db_add_recipe(const std::string &name, const std::string &description) { + if(not db) return -1; - add_query = malloc(strlen(name) + strlen(description) + strlen(add_query_fmt) + 1); - sprintf(add_query, add_query_fmt, name, description); - if(sqlite3_exec(db, add_query, NULL, NULL, NULL) != SQLITE_OK) { - free(add_query); + if(sqlite3_exec(db, std::format("INSERT INTO recipes(name,description) VALUES('{}','{}');", name, description).c_str(), + NULL, NULL, NULL) != SQLITE_OK) return -2; - } - free(add_query); return db_get_recipe_id(name); } -int db_get_recipe_id(const char *name) { - if(!db) +int db_get_recipe_id(const std::string &name) { + if(not db) return -1; return table_get_id_by_name("recipes", name); } -int db_add_ingredient(const char *name) { - const char *add_query_fmt = "INSERT INTO ingredients(name) VALUES(lower('%s'));"; - char *add_query; - - if(!db) +int db_add_ingredient(const std::string &name) { + if(not db) return -1; - add_query = malloc(strlen(name) + strlen(add_query_fmt) + 1); - sprintf(add_query, add_query_fmt, name); - if(sqlite3_exec(db, add_query, NULL, NULL, NULL) != SQLITE_OK) { - free(add_query); + if(sqlite3_exec(db, std::format("INSERT INTO ingredients(name) VALUES(lower('{}'));", name).c_str(), + NULL, NULL, NULL) != SQLITE_OK) return -2; - } - free(add_query); return db_get_ingredient_id(name); } -int db_get_ingredient_id(const char *name) { - if(!db) +int db_get_ingredient_id(const std::string &name) { + if(not db) return -1; return table_get_id_by_name("ingredients", name); } -int db_add_tag(const char *name) { - const char *add_query_fmt = "INSERT INTO tags(name) VALUES('%s');"; - char *add_query; - - if(!db) +int db_add_tag(const std::string &name) { + if(not db) return -1; - add_query = malloc(strlen(name) + strlen(add_query_fmt) + 1); - sprintf(add_query, add_query_fmt, name); - if(sqlite3_exec(db, add_query, NULL, NULL, NULL) != SQLITE_OK) { - free(add_query); + if(sqlite3_exec(db, std::format("INSERT INTO tags(name) VALUES('{}');", name).c_str(), + NULL, NULL, NULL) != SQLITE_OK) return -2; - } - free(add_query); return db_get_tag_id(name); } -int db_get_tag_id(const char *name) { - if(!db) +int db_get_tag_id(const std::string &name) { + if(not db) return -1; return table_get_id_by_name("tags", name); } int db_conn_recipe_ingredient(int recipe_id, int ingredient_id) { - const char *add_conn_fmt = "INSERT INTO recipe_ingredient(recipe_id, ingredient_id) VALUES(%d,%d);"; - char *add_conn_query; - - if(!db) + if(not db) return -1; - add_conn_query = malloc(strlen(add_conn_fmt) + (recipe_id % 10) + (ingredient_id % 10)); - sprintf(add_conn_query, add_conn_fmt, recipe_id, ingredient_id); - if(sqlite3_exec(db, add_conn_query, NULL, NULL, NULL) != SQLITE_OK) { - free(add_conn_query); + if(sqlite3_exec(db, std::format("INSERT INTO recipe_ingredient(recipe_id, ingredient_id) VALUES({},{});", recipe_id, ingredient_id).c_str(), + NULL, NULL, NULL) != SQLITE_OK) return -2; - } - free(add_conn_query); return 1; } int db_conn_recipe_tag(int recipe_id, int tag_id) { - const char *add_conn_fmt = "INSERT INTO recipe_tag(recipe_id, tag_id) VALUES(%d,%d);"; - char *add_conn_query; - - if(!db) + if(not db) return -1; - add_conn_query = malloc(strlen(add_conn_fmt) + (recipe_id % 10) + (tag_id % 10)); - sprintf(add_conn_query, add_conn_fmt, recipe_id, tag_id); - if(sqlite3_exec(db, add_conn_query, NULL, NULL, NULL) != SQLITE_OK) { - free(add_conn_query); + if(sqlite3_exec(db, std::format("INSERT INTO recipe_tag(recipe_id, tag_id) VALUES({},{});", recipe_id, tag_id).c_str(), + NULL, NULL, NULL) != SQLITE_OK) return -2; - } - free(add_conn_query); return 1; } diff --git a/src/db.hpp b/src/db.hpp index 0b70e82..6f3bba7 100644 --- a/src/db.hpp +++ b/src/db.hpp @@ -17,6 +17,8 @@ */ #pragma once +#include + int db_open(void); void db_close(void); @@ -28,9 +30,9 @@ void db_close(void); * * @return ID of newly created recipe, -1 if DB isn't open, -2 on other failure. */ -int db_add_recipe(const char *name, const char *description); -int db_get_recipe_id(const char *name); -static inline int db_recipe_exists(const char *name) { +int db_add_recipe(const std::string &name, const std::string &description); +int db_get_recipe_id(const std::string &name); +static inline int db_recipe_exists(const std::string &name) { return (db_get_recipe_id(name) > 0); } @@ -41,9 +43,9 @@ static inline int db_recipe_exists(const char *name) { * * @return ID of newly created ingredient, -1 if DB isn't open, -2 on other failure. */ -int db_add_ingredient(const char *name); -int db_get_ingredient_id(const char *name); -static inline int db_ingredient_exists(const char *name) { +int db_add_ingredient(const std::string &name); +int db_get_ingredient_id(const std::string &name); +static inline int db_ingredient_exists(const std::string &name) { return (db_get_ingredient_id(name) > 0); } @@ -54,9 +56,9 @@ static inline int db_ingredient_exists(const char *name) { * * @return ID of newly created tag, -1 if DB isn't open, -2 on other failure. */ -int db_add_tag(const char *name); -int db_get_tag_id(const char *name); -static inline int db_tag_exists(const char *name) { +int db_add_tag(const std::string &name); +int db_get_tag_id(const std::string &name); +static inline int db_tag_exists(const std::string &name) { return (db_get_tag_id(name) > 0); } diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..46e5430 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Nicolás Ortega Froysa + * Nicolás Ortega Froysa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "util.hpp" +#include +#include + +std::vector split(std::string str, const std::string &delim) { + std::vector result; + std::string substr; + size_t pos = 0; + + while((pos = str.find(delim)) not_eq std::string::npos) { + substr = str.substr(0, pos); + result.push_back(substr); + str.erase(0, pos + delim.size()); + } + result.push_back(str); + + return result; +} + +void trim(std::string &str) { + str.erase(str.begin(), + std::find_if(str.begin(), str.end(), [](char c) { + return not std::isspace(c); + })); + str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) { + return not std::isspace(c); + }).base(), str.end()); +} diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..262045d --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Nicolás Ortega Froysa + * Nicolás Ortega Froysa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +std::vector split(std::string str, const std::string &delim); +void trim(std::string &str);