Make things work #1
|
@ -0,0 +1,3 @@
|
|||
# clangd currently doesnt support modules
|
||||
CompileFlags:
|
||||
Remove: [-fmodule*, -fdeps-format*]
|
|
@ -1,3 +1,4 @@
|
|||
cmake-build*
|
||||
|
||||
build
|
||||
.cache
|
||||
.idea
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "external/ctre"]
|
||||
path = external/ctre
|
||||
url = https://github.com/hanickadot/compile-time-regular-expressions.git
|
|
@ -2,13 +2,43 @@ cmake_minimum_required(VERSION 3.30)
|
|||
project(nutri)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 26)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
|
||||
set(CMAKE_CXX_FLAGS -fdiagnostics-color=always)
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(cpr REQUIRED)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
include(FetchContent)
|
||||
|
||||
include_directories(${CMAKE_SOURCE_DIR}/external/ctre/include)
|
||||
add_subdirectory(external/ctre/)
|
||||
find_package(ctre QUIET)
|
||||
if (NOT ctre_FOUND)
|
||||
FetchContent_Declare(
|
||||
ctre
|
||||
GIT_REPOSITORY https://github.com/hanickadot/compile-time-regular-expressions.git
|
||||
GIT_TAG v3.9.0
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(ctre)
|
||||
endif()
|
||||
|
||||
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(
|
||||
nutri
|
||||
|
@ -17,6 +47,6 @@ add_executable(
|
|||
src/Helpers/Utility.cpp
|
||||
src/Helpers/Utility.hpp
|
||||
src/Helpers/WolframAlpha.hpp
|
||||
src/Helpers/WolframAlpha.cpp)
|
||||
)
|
||||
|
||||
target_link_libraries(nutri PRIVATE cpr::cpr nlohmann_json::nlohmann_json ctre::ctre)
|
||||
target_link_libraries(nutri PRIVATE cpr::cpr ctre::ctre glaze::glaze)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 626313dd1563e6f3a02e35427052d808df636cd8
|
|
@ -1,41 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class ArgParser {
|
||||
public:
|
||||
void addArg(const std::string& shortOpt, const std::string& longOpt, const std::string& description, bool hasValue = true) {
|
||||
m_arguments.emplace_back(shortOpt, longOpt, description, "", hasValue);
|
||||
if (!shortOpt.empty()) {
|
||||
m_shortOptMap[shortOpt] = &m_arguments.back();
|
||||
}
|
||||
if (!longOpt.empty()) {
|
||||
m_longOptMap[longOpt] = &m_arguments.back();
|
||||
}
|
||||
void add(const std::string& shortOpt, const std::string& longOpt, const std::string& description, bool requiresValue = true) {
|
||||
Argument arg{shortOpt, longOpt, description, std::nullopt, requiresValue, false};
|
||||
m_arguments.push_back(arg);
|
||||
if (!shortOpt.empty())
|
||||
m_optMap[shortOpt] = &m_arguments.back();
|
||||
if (!longOpt.empty())
|
||||
m_optMap[longOpt] = &m_arguments.back();
|
||||
}
|
||||
|
||||
bool parse(const int argc, char* argv[]) {
|
||||
for (int i{1}; i < argc; ++i) {
|
||||
if (const std::string arg{argv[i]}; m_shortOptMap.contains(arg)) {
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
bool parse(int argc, char* argv[]) {
|
||||
if (argc == 1) {
|
||||
printHelp();
|
||||
return false;
|
||||
}
|
||||
m_shortOptMap[arg]->exists = true;
|
||||
if (m_shortOptMap[arg]->hasValue && i + 1 < argc) {
|
||||
m_shortOptMap[arg]->value = argv[++i];
|
||||
}
|
||||
} else if (m_longOptMap.contains(arg)) {
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
printHelp();
|
||||
return false;
|
||||
}
|
||||
m_longOptMap[arg]->exists = true;
|
||||
if (m_longOptMap[arg]->hasValue && i + 1 < argc) {
|
||||
m_longOptMap[arg]->value = argv[++i];
|
||||
|
||||
if (auto* argument = findArgument(arg); argument) {
|
||||
argument->exists = true;
|
||||
if (argument->requiresValue) {
|
||||
if (i + 1 >= argc)
|
||||
throw std::invalid_argument("Missing value for " + arg);
|
||||
argument->value = argv[++i];
|
||||
}
|
||||
} else {
|
||||
throw std::invalid_argument("Unknown option: " + arg);
|
||||
|
@ -44,6 +46,20 @@ public:
|
|||
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 {
|
||||
std::println("Usage: [options]");
|
||||
for (const auto& arg : m_arguments) {
|
||||
|
@ -51,48 +67,41 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get(const std::string& arg) const {
|
||||
std::string value;
|
||||
if (m_longOptMap.contains(arg)) {
|
||||
value = m_longOptMap.at(arg)->value;
|
||||
} else if (m_shortOptMap.contains(arg)) {
|
||||
value = m_shortOptMap.at(arg)->value;
|
||||
} else {
|
||||
throw std::runtime_error(arg + " has no value");
|
||||
}
|
||||
if constexpr (std::is_same_v<T, int>) {
|
||||
return std::stoi(value);
|
||||
} else if constexpr (std::is_same_v<T, float>) {
|
||||
return std::stof(value);
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
return std::stod(value);
|
||||
} else {
|
||||
return 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:
|
||||
struct Argument {
|
||||
std::string shortOpt;
|
||||
std::string longOpt;
|
||||
std::string description;
|
||||
std::string value;
|
||||
bool hasValue = true;
|
||||
std::optional<std::string> value;
|
||||
bool requiresValue;
|
||||
bool exists = false;
|
||||
};
|
||||
|
||||
std::vector<Argument> m_arguments{};
|
||||
std::unordered_map<std::string, Argument*> m_shortOptMap{};
|
||||
std::unordered_map<std::string, Argument*> m_longOptMap{};
|
||||
Argument* findArgument(const std::string& opt) {
|
||||
if (auto it = m_optMap.find(opt); it != m_optMap.end())
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
#include "WolframAlpha.hpp"
|
||||
|
||||
#include "Utility.hpp"
|
||||
#include <cpr/cpr.h>
|
||||
#include <ctre.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
WolframAlpha::WolframAlpha(const std::string& query, double amount) : m_json(fetchJson(query)), m_amount(amount) {}
|
||||
|
||||
double WolframAlpha::getCalories() {
|
||||
if (auto match = ctre::search<R"((?<=total calories\s)\d+)">(getPod())) {
|
||||
return std::stod(match.data()) * m_amount;
|
||||
}
|
||||
throw std::runtime_error("Calorie information not found.");
|
||||
}
|
||||
|
||||
double WolframAlpha::getProtein() {
|
||||
if (auto match = ctre::search<R"((?<=protein\s)\d+\s*\w+)">(getPod())) {
|
||||
const std::string val = std::string(match.to_view().substr(0, match.begin() - match.end()));
|
||||
std::string unit = std::string(ctre::search<R"(\w+$)">(match.to_view()));
|
||||
|
||||
return unit == "g" ? std::stod(val) * m_amount : std::stod(val) / 1000 * m_amount;
|
||||
}
|
||||
throw std::runtime_error("Protein information not found.");
|
||||
}
|
||||
|
||||
double WolframAlpha::getCarbs() {
|
||||
if (auto match = ctre::search<R"((?<=total carbohydrates\s)\d+\s*\w+)">(getPod())) {
|
||||
const std::string val = std::string(match.to_view().substr(0, match.begin() - match.end()));
|
||||
std::string unit = std::string(ctre::search<R"(\w+$)">(match.to_view()));
|
||||
|
||||
return unit == "g" ? std::stod(val) * m_amount : std::stod(val) / 1000 * m_amount;
|
||||
}
|
||||
throw std::runtime_error("Carbohydrates information not found.");
|
||||
}
|
||||
|
||||
double WolframAlpha::getFat() {
|
||||
if (auto match = ctre::search<R"((?<=total fat\s)\d+\s*\w+)">(getPod())) {
|
||||
const std::string val = std::string(match.to_view().substr(0, match.begin() - match.end()));
|
||||
std::string unit = std::string(ctre::search<R"(\w+$)">(match.to_view()));
|
||||
|
||||
return unit == "g" ? std::stod(val) * m_amount : std::stod(val) / 1000 * m_amount;
|
||||
}
|
||||
throw std::runtime_error("Fat information not found.");
|
||||
}
|
||||
|
||||
nlohmann::json WolframAlpha::fetchJson(const std::string& query) {
|
||||
const auto WOLFRAM_KEY = utl::getEnv("NUTRI_WA_KEY");
|
||||
cpr::Response json = Get(cpr::Url{"https://api.wolframalpha.com/v2/query"},
|
||||
cpr::Parameters{{"input", query + "100g nutrition"}, {"appid", WOLFRAM_KEY}, {"output", "JSON"}, {"format", "plaintext"}});
|
||||
return nlohmann::json::parse(json.text);
|
||||
}
|
||||
|
||||
std::string WolframAlpha::getPod() {
|
||||
return to_string(m_json["queryresult"]["pods"][1]["subpods"][0]["plaintext"]);
|
||||
}
|
||||
|
||||
nlohmann::json m_json{};
|
||||
double m_amount{};
|
|
@ -1,21 +1,89 @@
|
|||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "Utility.hpp"
|
||||
#include "ctll/fixed_string.hpp"
|
||||
#include <cpr/cpr.h>
|
||||
#include <ctll.hpp>
|
||||
#include <ctre.hpp>
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
struct Data {
|
||||
double cals{};
|
||||
double proteins{};
|
||||
double carbs{};
|
||||
double fats{};
|
||||
};
|
||||
|
||||
class WolframAlpha {
|
||||
public:
|
||||
explicit WolframAlpha(const std::string& query, double amount);
|
||||
WolframAlpha(const std::string& query, double amount) : m_plaintext{initPlaintext(query)}, m_amount{amount} {
|
||||
initData();
|
||||
}
|
||||
|
||||
double getCalories();
|
||||
double getProtein();
|
||||
double getCarbs();
|
||||
double getFat();
|
||||
void print() {
|
||||
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:
|
||||
nlohmann::json fetchJson(const std::string& query);
|
||||
std::string getPod();
|
||||
void initData() {
|
||||
const auto convertToGrams = [this](const double& match, const std::string& unit) {
|
||||
if (unit == "g") {
|
||||
return match * m_amount;
|
||||
} else if (unit == "mg") {
|
||||
return (match / 1000) * m_amount;
|
||||
} else {
|
||||
throw std::invalid_argument("Unsupported unit: " + unit);
|
||||
}
|
||||
};
|
||||
|
||||
nlohmann::json m_json{};
|
||||
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) {
|
||||
m_data.proteins = convertToGrams(match.get<1>().to_number(), match.get<2>().to_string());
|
||||
}
|
||||
|
||||
if (auto match = ctre::search<carbs_regex>(m_plaintext); match) {
|
||||
m_data.carbs = convertToGrams(match.get<1>().to_number(), match.get<2>().to_string());
|
||||
}
|
||||
|
||||
if (auto match = ctre::search<fats_regex>(m_plaintext); match) {
|
||||
m_data.fats = convertToGrams(match.get<1>().to_number(), match.get<2>().to_string());
|
||||
}
|
||||
|
||||
if (auto match = ctre::search<calories_regex>(m_plaintext); match) {
|
||||
m_data.cals = match.get<0>().to_number() * m_amount;
|
||||
}
|
||||
}
|
||||
|
||||
std::string initPlaintext(const std::string& query) {
|
||||
const auto WOLFRAM_KEY = utl::getEnv("NUTRI_WA_KEY");
|
||||
|
||||
// clang-format off
|
||||
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::parse_error && json["queryresult"]["success"].get_boolean()) {
|
||||
return json["queryresult"]["pods"][1]["subpods"][0]["plaintext"].get<std::string>();
|
||||
} else {
|
||||
throw std::invalid_argument("WolframAlpha query failed");
|
||||
}
|
||||
}
|
||||
|
||||
Data m_data{};
|
||||
std::string m_plaintext{};
|
||||
double m_amount{};
|
||||
};
|
||||
|
|
19
src/main.cpp
19
src/main.cpp
|
@ -1,25 +1,24 @@
|
|||
#include "ArgParser.hpp"
|
||||
#include "Helpers/WolframAlpha.hpp"
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <print>
|
||||
|
||||
int main(const int argc, char* argv[]) {
|
||||
try {
|
||||
ArgParser args;
|
||||
args.addArg("-s", "--source", "Specify the source of the data (Wolfram|OpenFoodFacts)");
|
||||
args.addArg("-f", "--food", "Specify the food item");
|
||||
args.addArg("-a", "--amount", "Specify the amount (in grams)");
|
||||
args.addArg("-h", "--help", "Print help", false);
|
||||
args.parse(argc, argv);
|
||||
args.add("-h", "--help", "Print help", false);
|
||||
args.add("-f", "--food", "Specify the food item");
|
||||
args.add("-a", "--amount", "Specify the amount (in grams)");
|
||||
|
||||
if (!args.parse(argc, argv)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto food = args.get<std::string>("--food");
|
||||
const double amount = args.get<double>("--amount") / 100;
|
||||
|
||||
if (!args.has("--source") || args.get<std::string>("--source") == "Wolfram") {
|
||||
WolframAlpha wa(food, amount);
|
||||
std::println("Calories: {:.2f} kcal\nProtein: {:.2f} g\nCarbs: {:.2f} g\nFat: {:.2f} g", wa.getCalories(), wa.getProtein(), wa.getCarbs(), wa.getFat());
|
||||
}
|
||||
|
||||
wa.print();
|
||||
} catch (const std::exception& e) {
|
||||
std::println(stderr, "Error: {}", e.what());
|
||||
return 1;
|
||||
|
|
Loading…
Reference in New Issue