This commit is contained in:
:^) 2024-11-10 18:26:07 +01:00
parent 579f82f4fb
commit 467f107809
No known key found for this signature in database
4 changed files with 166 additions and 131 deletions

View File

@ -3,38 +3,50 @@ project(nutri)
set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD 26)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
set(CMAKE_CXX_FLAGS -fdiagnostics-color=always)
find_package(nlohmann_json REQUIRED)
include(FetchContent) include(FetchContent)
FetchContent_Declare( find_package(ctre QUIET)
ctre if (NOT ctre_FOUND)
GIT_REPOSITORY https://github.com/hanickadot/compile-time-regular-expressions.git FetchContent_Declare(
GIT_TAG v3.9.0 ctre
GIT_SHALLOW TRUE GIT_REPOSITORY https://github.com/hanickadot/compile-time-regular-expressions.git
) GIT_TAG v3.9.0
FetchContent_Declare( GIT_SHALLOW TRUE
cpr )
GIT_REPOSITORY https://github.com/libcpr/cpr.git FetchContent_MakeAvailable(ctre)
GIT_TAG 1.11.0 endif()
GIT_SHALLOW TRUE
)
FetchContent_Declare(
glaze
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG main
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(ctre cpr glaze) find_package(cpr QUIET)
if (NOT cpr_FOUND)
FetchContent_Declare(
cpr
GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 1.11.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(cpr)
endif()
find_package(glaze QUIET)
if (NOT glaze_FOUND)
FetchContent_Declare(
glaze
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG main
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glaze)
endif()
add_executable( add_executable(
nutri nutri
src/main.cpp src/main.cpp
src/ArgParser.hpp src/ArgParser.hpp
src/Helpers/Utility.cpp src/Helpers/Utility.cpp
src/Helpers/Utility.hpp src/Helpers/Utility.hpp
src/Helpers/WolframAlpha.hpp src/Helpers/WolframAlpha.hpp
) )
target_link_libraries(nutri PRIVATE cpr::cpr nlohmann_json::nlohmann_json ctre::ctre glaze::glaze)
target_link_libraries(nutri PRIVATE cpr::cpr ctre::ctre glaze::glaze)

View File

@ -1,41 +1,43 @@
#pragma once #pragma once
#include <optional>
#include <print> #include <print>
#include <stdexcept>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
class ArgParser { class ArgParser {
public: public:
void addArg(const std::string& shortOpt, const std::string& longOpt, const std::string& description, bool hasValue = true) { void add(const std::string& shortOpt, const std::string& longOpt, const std::string& description, bool requiresValue = true) {
m_arguments.emplace_back(shortOpt, longOpt, description, "", hasValue); Argument arg{shortOpt, longOpt, description, std::nullopt, requiresValue, false};
if (!shortOpt.empty()) { m_arguments.push_back(arg);
m_shortOptMap[shortOpt] = &m_arguments.back(); if (!shortOpt.empty())
} m_optMap[shortOpt] = &m_arguments.back();
if (!longOpt.empty()) { if (!longOpt.empty())
m_longOptMap[longOpt] = &m_arguments.back(); m_optMap[longOpt] = &m_arguments.back();
}
} }
bool parse(const int argc, char* argv[]) { bool parse(int argc, char* argv[]) {
for (int i{1}; i < argc; ++i) { if (argc == 1) {
if (const std::string arg{argv[i]}; m_shortOptMap.contains(arg)) { printHelp();
if (arg == "--help" || arg == "-h") { return false;
printHelp(); }
return false;
} for (int i = 1; i < argc; ++i) {
m_shortOptMap[arg]->exists = true; std::string arg = argv[i];
if (m_shortOptMap[arg]->hasValue && i + 1 < argc) {
m_shortOptMap[arg]->value = argv[++i]; if (arg == "-h" || arg == "--help") {
} printHelp();
} else if (m_longOptMap.contains(arg)) { return false;
if (arg == "--help" || arg == "-h") { }
printHelp();
return false; if (auto* argument = findArgument(arg); argument) {
} argument->exists = true;
m_longOptMap[arg]->exists = true; if (argument->requiresValue) {
if (m_longOptMap[arg]->hasValue && i + 1 < argc) { if (i + 1 >= argc)
m_longOptMap[arg]->value = argv[++i]; throw std::invalid_argument("Missing value for " + arg);
argument->value = argv[++i];
} }
} else { } else {
throw std::invalid_argument("Unknown option: " + arg); throw std::invalid_argument("Unknown option: " + arg);
@ -44,6 +46,20 @@ public:
return true; return true;
} }
template <typename T>
T get(const std::string& arg) const {
const Argument* argument = findArgument(arg);
if (!argument || !argument->value)
throw std::runtime_error(arg + " has no value");
return convertToType<T>(*argument->value);
}
bool has(const std::string& arg) const {
const Argument* argument = findArgument(arg);
return argument && argument->exists;
}
void printHelp() const { void printHelp() const {
std::println("Usage: [options]"); std::println("Usage: [options]");
for (const auto& arg : m_arguments) { for (const auto& arg : m_arguments) {
@ -51,44 +67,41 @@ public:
} }
} }
template <typename T>
T get(const std::string& arg) const {
std::string value;
if (m_longOptMap.contains(arg) && !m_longOptMap.at(arg)->value.empty()) {
value = m_longOptMap.at(arg)->value;
} else if (m_shortOptMap.contains(arg) && !m_shortOptMap.at(arg)->value.empty()) {
value = m_shortOptMap.at(arg)->value;
} else {
throw std::runtime_error(arg + " has no value");
}
if constexpr (std::is_same_v<T, std::string>) {
return value;
} else {
return std::stoi(value);
}
}
bool has(const std::string& arg) const {
if (const auto longOptIt = m_longOptMap.find(arg); longOptIt != m_longOptMap.end()) {
return longOptIt->second->exists;
}
if (const auto shortOptIt = m_shortOptMap.find(arg); shortOptIt != m_shortOptMap.end()) {
return shortOptIt->second->exists;
}
return false;
}
private: private:
struct Argument { struct Argument {
std::string shortOpt; std::string shortOpt;
std::string longOpt; std::string longOpt;
std::string description; std::string description;
std::string value; std::optional<std::string> value;
bool hasValue = true; bool requiresValue;
bool exists = false; bool exists = false;
}; };
std::vector<Argument> m_arguments{}; Argument* findArgument(const std::string& opt) {
std::unordered_map<std::string, Argument*> m_shortOptMap{}; if (auto it = m_optMap.find(opt); it != m_optMap.end())
std::unordered_map<std::string, Argument*> m_longOptMap{}; return it->second;
return nullptr;
}
const Argument* findArgument(const std::string& opt) const {
if (auto it = m_optMap.find(opt); it != m_optMap.end())
return it->second;
return nullptr;
}
template <typename T>
T convertToType(const std::string& value) const {
if constexpr (std::is_same_v<T, std::string>) {
return value;
} else if constexpr (std::is_same_v<T, int>) {
return std::stoi(value);
} else if constexpr (std::is_same_v<T, double>) {
return std::stod(value);
} else {
throw std::runtime_error("Unsupported type for argument conversion");
}
}
std::vector<Argument> m_arguments;
std::unordered_map<std::string, Argument*> m_optMap;
}; };

View File

@ -6,11 +6,10 @@
#include <ctll.hpp> #include <ctll.hpp>
#include <ctre.hpp> #include <ctre.hpp>
#include <glaze/glaze.hpp> #include <glaze/glaze.hpp>
#include <print>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
// Example regex patern: (?<=protein\s)(\d+)\s(\w)
struct Data { struct Data {
double cals{}; double cals{};
double proteins{}; double proteins{};
@ -20,61 +19,71 @@ struct Data {
class WolframAlpha { class WolframAlpha {
public: public:
WolframAlpha(const std::string& query, double amount) : m_plaintext{getData(query)}, m_amount{amount / 100} {} WolframAlpha(const std::string& query, double amount) : m_plaintext{initPlaintext(query)}, m_amount{amount} {
initData();
//debug
std::string getPlain() {
return m_plaintext;
} }
Data getNutrition() { void print() {
return m_data; std::println("Cals: {:g} kcal\nProtein: {:g} g\nCarbs: {:g} g\nFats: {:g} g", m_data.cals, m_data.proteins, m_data.carbs, m_data.fats);
} }
private: private:
void getMacro() { void initData() {
static constexpr ctll::fixed_string calories_regex{R"((?<=total calories\s)\d+)"}; const auto convertToGrams = [this](const double& match, const std::string& unit) {
static constexpr ctll::fixed_string proteins_regex{R"((?<=protein\s)(\d+)\s(\w))"}; if (unit == "g") {
static constexpr ctll::fixed_string carbs_regex{R"((?<=total carbohydrates\s)(\d+)\s(\w))"}; return match * m_amount;
static constexpr ctll::fixed_string fats_regex{R"((?<=total fat\s)(\d+)\s(\w))"}; } else if (unit == "mg") {
return (match / 1000) * m_amount;
const auto convertToGrams = [this](const auto& match, const std::string& unit) -> double { } else {
const double value = (unit == "g") ? match.to_number() : match.to_number() / 1000; throw std::invalid_argument("Unsupported unit: " + unit);
return value * m_amount; }
}; };
constexpr ctll::fixed_string calories_regex{R"((?<=total calories\s)\d+)"};
constexpr ctll::fixed_string proteins_regex{R"((?<=protein\s)(\d+)\s(\w+))"};
constexpr ctll::fixed_string carbs_regex{R"((?<=total carbohydrates\s)(\d+)\s(\w+))"};
constexpr ctll::fixed_string fats_regex{R"((?<=total fat\s)(\d+)\s(\w+))"};
if (auto match = ctre::search<proteins_regex>(m_plaintext); match) { if (auto match = ctre::search<proteins_regex>(m_plaintext); match) {
m_data.proteins = convertToGrams(match.get<1>(), match.get<2>().to_string()); m_data.proteins = convertToGrams(match.get<1>().to_number(), match.get<2>().to_string());
} }
if (auto match = ctre::search<carbs_regex>(m_plaintext); match) { if (auto match = ctre::search<carbs_regex>(m_plaintext); match) {
m_data.carbs = convertToGrams(match.get<1>(), match.get<2>().to_string()); m_data.carbs = convertToGrams(match.get<1>().to_number(), match.get<2>().to_string());
} }
if (auto match = ctre::search<fats_regex>(m_plaintext); match) { if (auto match = ctre::search<fats_regex>(m_plaintext); match) {
m_data.fats = convertToGrams(match.get<1>(), match.get<2>().to_string()); m_data.fats = convertToGrams(match.get<1>().to_number(), match.get<2>().to_string());
} }
if (auto match = ctre::search<calories_regex>(m_plaintext); match) { if (auto match = ctre::search<calories_regex>(m_plaintext); match) {
m_data.cals = match.get<0>().to_number(); m_data.cals = match.get<0>().to_number() * m_amount;
} }
} }
std::string getData(const std::string& query) {
const auto WOLFRAM_KEY = utl::getEnv("NUTRI_WA_KEY");
cpr::Response response = Get(cpr::Url{"https://api.wolframalpha.com/v2/query"},
cpr::Parameters{{"input", query + "100g nutrition"}, {"appid", WOLFRAM_KEY}, {"output", "JSON"}, {"format", "plaintext"}});
glz::json_t json{}; std::string initPlaintext(const std::string& query) {
// Pretty sure this is wrong and i always is false const auto WOLFRAM_KEY = utl::getEnv("NUTRI_WA_KEY");
if (glz::read_json(json, response.text)) {
throw std::runtime_error("Failed to parse the JSON"); // clang-format off
} else { cpr::Response response = Get(
cpr::Url{"https://api.wolframalpha.com/v2/query"},
cpr::Parameters{
{"input", query + "100g nutrition"},
{"appid", WOLFRAM_KEY},
{"output", "JSON"},
{"format", "plaintext"}});
// clang-format on
glz::json_t json{};
auto ec = glz::read_json(json, response.text);
if (ec != glz::error_code::missing_key) {
return json["queryresult"]["pods"][1]["subpods"][0]["plaintext"].get<std::string>(); return json["queryresult"]["pods"][1]["subpods"][0]["plaintext"].get<std::string>();
} else {
throw std::invalid_argument("JSON Key not found");
} }
} }
Data m_data{}; Data m_data{};
std::string m_pod{};
std::string m_plaintext{}; std::string m_plaintext{};
double m_amount{}; double m_amount{};
}; };

View File

@ -5,19 +5,20 @@
int main(const int argc, char* argv[]) { int main(const int argc, char* argv[]) {
try { try {
ArgParser args; ArgParser args;
args.addArg("-f", "--food", "Specify the food item"); args.add("-h", "--help", "Print help", false);
args.addArg("-a", "--amount", "Specify the amount (in grams)"); args.add("-f", "--food", "Specify the food item");
args.addArg("-h", "--help", "Print help", false); args.add("-a", "--amount", "Specify the amount (in grams)");
args.parse(argc, argv);
if (!args.parse(argc, argv)) {
return 0;
}
const auto food = args.get<std::string>("--food"); const auto food = args.get<std::string>("--food");
const double amount = args.get<double>("--amount") / 100; const double amount = args.get<double>("--amount") / 100;
WolframAlpha wa(food, amount); WolframAlpha wa(food, amount);
auto a = wa.getNutrition(); wa.print();
std::println("cals: {}\nprotein: {}\ncarbs: {}\nfat: {}", a.cals, a.proteins, a.carbs, a.fats);
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::println(stderr, "Error: {}", e.what()); std::println(stderr, "Error: {}", e.what());
return 1; return 1;