From 8b1f1374d8d9fa65946e344a6fe0529e41b9490c Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Sat, 27 May 2023 11:28:58 -0600 Subject: [PATCH] More refactoring and bug fixes - Added `libhttp` and `librest_api` targets to CMakeLists.txt - Added examples for `libhttp` and `librest_api` in examples directory - Added `cache` and `add` commands - Added documentation for `libhttp` and `librest_api` - Added all available HTTP response codes to REST API - Changed how `bin/compile.sh` works (must supply args to build) --- CMakeLists.txt | 8 +++ bin/compile.sh | 72 ++++++++++++++++------ examples/http/README.md | 15 +++++ examples/http/main.cpp | 9 +++ examples/rest_api/README.md | 26 ++++++++ examples/rest_api/main.cpp | 8 +++ include/cache.hpp | 10 +-- include/concepts.hpp | 6 ++ include/config.hpp | 2 +- include/error.hpp | 16 +++-- include/http.hpp | 77 +++++++++++++++++++++-- include/log.hpp | 89 +++++++++++++++++++++++---- include/package.hpp | 31 ++++++---- include/package_manager.hpp | 13 ++-- include/plugin.hpp | 10 +-- include/rest_api.hpp | 38 ++++++------ include/types.hpp | 61 +++++++++++++++++- src/config.cpp | 4 +- src/http.cpp | 21 +++++-- src/main.cpp | 10 +-- src/package.cpp | 92 ++++++++++++++++++++++------ src/package_manager.cpp | 119 ++++++++++++++++++++++++------------ src/rest_api.cpp | 2 +- 23 files changed, 575 insertions(+), 164 deletions(-) create mode 100644 examples/http/README.md create mode 100644 examples/http/main.cpp create mode 100644 examples/rest_api/README.md create mode 100644 examples/rest_api/main.cpp create mode 100644 include/concepts.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d4afafd..e9bcb56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ endif() # Get source files except for main.cpp file(GLOB SRC CONFIG_DEPENDS "src/[!main]*.cpp") file(GLOB TESTS CONFIG_DEPENDS "tests/*.cpp") +set(SRC_HTTP "src/http.cpp") +set(SRC_RESTAPI "src/rest_api.cpp") # Find all the packages required to build find_package(Threads REQUIRED) @@ -50,6 +52,8 @@ set(LINK_LIBS # Set library and executable targets add_library(${PROJECT_NAME}-shared SHARED "${SRC}") add_library(${PROJECT_NAME}-static STATIC "${SRC}") +add_library(${PROJECT_NAME}-http SHARED "${SRC_HTTP}") +add_library(${PROJECT_NAME}-restapi SHARED "${SRC_RESTAPI}") add_executable(${PROJECT_NAME} "src/main.cpp") add_executable(${PROJECT_NAME}.static "src/main.cpp") add_executable(${PROJECT_NAME}.tests "${TESTS}") @@ -60,6 +64,8 @@ target_include_directories(${PROJECT_NAME}.static PRIVATE ${INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME}.tests PRIVATE ${INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME}-shared PRIVATE ${INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME}-static PRIVATE ${INCLUDE_DIRS}) +target_include_directories(${PROJECT_NAME}-http PRIVATE ${INCLUDE_DIRS}) +target_include_directories(${PROJECT_NAME}-restapi PRIVATE ${INCLUDE_DIRS}) # Set link libraries for targets target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS}) @@ -67,6 +73,8 @@ target_link_libraries(${PROJECT_NAME}.static PRIVATE ${PROJECT_NAME}-static ${LI target_link_libraries(${PROJECT_NAME}.tests PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS}) target_link_libraries(${PROJECT_NAME}-shared PRIVATE ${LINK_LIBS}) target_link_libraries(${PROJECT_NAME}-static PRIVATE ${LINK_LIBS}) +target_link_libraries(${PROJECT_NAME}-http PRIVATE ${LINK_LIBS}) +target_link_libraries(${PROJECT_NAME}-restapi PRIVATE ${LINK_LIBS}) # Add project unit tests # add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS}) diff --git a/bin/compile.sh b/bin/compile.sh index 3e9e1f5..cd445c0 100755 --- a/bin/compile.sh +++ b/bin/compile.sh @@ -5,7 +5,7 @@ exe=gdpm static=gdpm.static tests=gdpm.tests -function test_link(){ +function link_exe(){ path=$1 link=$2 if test -f "$path" @@ -19,7 +19,7 @@ function test_link(){ fi } -function test_strip(){ +function strip(){ path=$1 if test -f "$path" then @@ -28,29 +28,65 @@ function test_strip(){ fi } +function generate_docs(){ + # Generate documentation using `doxygen` + cd ${script_dir}/.. + doxygen +} + +function strip_all(){ + # Strip debug symbols + strip ${script_dir}/../build/gdpm + strip ${script_dir}/../build/gdpm.static + strip ${script_dir}/../build/gdpm.tests +} + +function link_all(){ + # Create symlinks to executables in build folder if necessary + link_exe $script_dir/../build/gdpm $script_dir/../bin/$exe + link_exe $script_dir/../build/gdpm.static $script_dir/../bin/$static + link_exe $script_dir/../build/gdpm.tests $script_dir/../bin/$tests +} + +function clean(){ + rm ${script_dir}/../bin/$exe + rm ${script_dir}/../bin/$static + rm ${script_dir}/../bin/$tests +} + # Run this script at project root #meson configure build #CXX=clang++ meson compile -C build -j$(proc) # CMake/ninja build system -mkdir -p build -cmake -B build -S . -D CMAKE_EXPORT_COMPILE_COMMANDS=1 -D CMAKE_BUILD_TYPE=Debug -G Ninja -ninja -C build -j $(nproc) +function build_binaries(){ + mkdir -p build + cmake -B build -S . -D CMAKE_EXPORT_COMPILE_COMMANDS=1 -D CMAKE_BUILD_TYPE=Debug -G Ninja + ninja -C build -j $(nproc) +} -# Create symlinks to executables in build folder if necessary -test_link $script_dir/../build/gdpm $script_dir/../bin/$exe -test_link $script_dir/../build/gdpm.static $script_dir/../bin/$static -test_link $script_dir/../build/gdpm.tests $script_dir/../bin/$tests +# Stop if no args +if [ $# -eq 0 ] +then + exit 1 +fi + +# Handle arguemnts passed in +i=$(($#-1)) +while [ $i -ge 0 ]; +do + case "$1" in + -a|--all) build_binaries shift;; + -c|--clean) clean shift;; + -d|--docs) generate_docs shift;; + -s|--strip) strip_all shift;; + -l|--link) link_all shift;; + --*) echo "Bad option: $1" + esac + i=$((i-1)) +done +exit 0 -# Strip debug symbols -test_strip ${script_dir}/../build/gdpm -test_strip ${script_dir}/../build/gdpm.static -test_strip ${script_dir}/../build/gdpm.tests - - -# Generate documentation using `doxygen` -cd ${script_dir}/.. -doxygen diff --git a/examples/http/README.md b/examples/http/README.md new file mode 100644 index 0000000..7f96320 --- /dev/null +++ b/examples/http/README.md @@ -0,0 +1,15 @@ +# GDPM HTTP Example + +This is an example showing how to use the GDPM HTTP library to download files. The library uses RapidJSON to get results. + + +Here is a quick snippet to get started: +```c++ +// Get a full list of assets +rest_api::context context = rest_api::make_context(); +rapidjson::Document doc = http::get_asset_list( + "https://godotengine.org/asset-library/api", + context +) +// ... parse the rapidjson::Document +``` \ No newline at end of file diff --git a/examples/http/main.cpp b/examples/http/main.cpp new file mode 100644 index 0000000..95dfa22 --- /dev/null +++ b/examples/http/main.cpp @@ -0,0 +1,9 @@ +/*! Example using the `libgdpm-http` library +*/ + +#include "http.hpp" + + +int main(int argc, char **argv){ + return 0; +} \ No newline at end of file diff --git a/examples/rest_api/README.md b/examples/rest_api/README.md new file mode 100644 index 0000000..d3911d5 --- /dev/null +++ b/examples/rest_api/README.md @@ -0,0 +1,26 @@ +# GDPM Rest API Example + +This is an example showing how to use the REST API designed to query the Godot Asset library in C++. It is built using the `libcurl` library. + +Here is a snippet making a HTTP get and post request. + +```c++ +using string = std::string; +using headers_t = std::unordered_map; + +std::string url = "www.example.com"; +http::response r_get = http::request_get(url) +if(r_get.response_code == http::response_code::OK){ + // ...do something... +} + +http::request_params params; +params.headers = { + {"user-agent", "firefox"}, + {"content-type", "application/json"} +} +http::response r_post = http::request_post(url, params); +if(r_post.response_code == http::response_code::OK){ + // ...do something... +} +``` \ No newline at end of file diff --git a/examples/rest_api/main.cpp b/examples/rest_api/main.cpp new file mode 100644 index 0000000..d01bb7e --- /dev/null +++ b/examples/rest_api/main.cpp @@ -0,0 +1,8 @@ +/*! Example using the `libgdpm-restapi` library */ + +#include "rest_api.hpp" + + +int main(int argc, char **argv){ + return 0; +} \ No newline at end of file diff --git a/include/cache.hpp b/include/cache.hpp index 038f035..75801e3 100644 --- a/include/cache.hpp +++ b/include/cache.hpp @@ -12,8 +12,8 @@ namespace gdpm::cache { struct params { - std::string cache_path = GDPM_PACKAGE_CACHE_PATH; - std::string table_name = GDPM_PACKAGE_CACHE_TABLENAME; + string cache_path = GDPM_PACKAGE_CACHE_PATH; + string table_name = GDPM_PACKAGE_CACHE_TABLENAME; }; error create_package_database(bool overwrite = false, const params& = params()); @@ -22,11 +22,11 @@ namespace gdpm::cache { result_t get_package_info_by_title(const package::title_list& package_titles, const params& params = cache::params()); result_t get_installed_packages(const params& = params()); error update_package_info(const package::info_list& packages, const params& = params()); - error update_sync_info(const std::vector& download_urls, const params& = params()); + error update_sync_info(const args_t& download_urls, const params& = params()); error delete_packages(const package::title_list& package_titles, const params& = params()); error delete_packages(const package::id_list& package_ids, const params& = params()); error drop_package_database(const params& = params()); - result_t to_values(const package::info& package); - result_t to_values(const package::info_list& packages); + result_t to_values(const package::info& package); + result_t to_values(const package::info_list& packages); } \ No newline at end of file diff --git a/include/concepts.hpp b/include/concepts.hpp new file mode 100644 index 0000000..86f7e91 --- /dev/null +++ b/include/concepts.hpp @@ -0,0 +1,6 @@ +#pragma once +#include + +namespace gdpm::concepts{ + template concept RequireMinArgs = requires (std::size_t min){ sizeof...(Args) > min; }; +} \ No newline at end of file diff --git a/include/config.hpp b/include/config.hpp index 5a5d6a2..fbe56aa 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -28,7 +28,7 @@ namespace gdpm::config{ string to_json(const context& params); error load(std::filesystem::path path, context& config, int verbose = 0); error save(std::filesystem::path path, const context& config, int verbose = 0); - context make_context(const std::string& username = GDPM_CONFIG_USERNAME, const string& password = GDPM_CONFIG_PASSWORD, const string& path = GDPM_CONFIG_PATH, const string& token = GDPM_CONFIG_TOKEN, const string& godot_version = GDPM_CONFIG_GODOT_VERSION, const string& packages_dir = GDPM_CONFIG_LOCAL_PACKAGES_DIR, const string& tmp_dir = GDPM_CONFIG_LOCAL_TMP_DIR, const string_map& remote_sources = {GDPM_CONFIG_REMOTE_SOURCES}, size_t threads = GDPM_CONFIG_THREADS, size_t timeout = 0, bool enable_sync = GDPM_CONFIG_ENABLE_SYNC, bool enable_file_logging = GDPM_CONFIG_ENABLE_FILE_LOGGING, int verbose = GDPM_CONFIG_VERBOSE); + context make_context(const string& username = GDPM_CONFIG_USERNAME, const string& password = GDPM_CONFIG_PASSWORD, const string& path = GDPM_CONFIG_PATH, const string& token = GDPM_CONFIG_TOKEN, const string& godot_version = GDPM_CONFIG_GODOT_VERSION, const string& packages_dir = GDPM_CONFIG_LOCAL_PACKAGES_DIR, const string& tmp_dir = GDPM_CONFIG_LOCAL_TMP_DIR, const string_map& remote_sources = {GDPM_CONFIG_REMOTE_SOURCES}, size_t threads = GDPM_CONFIG_THREADS, size_t timeout = 0, bool enable_sync = GDPM_CONFIG_ENABLE_SYNC, bool enable_file_logging = GDPM_CONFIG_ENABLE_FILE_LOGGING, int verbose = GDPM_CONFIG_VERBOSE); error validate(const rapidjson::Document& doc); extern context config; diff --git a/include/error.hpp b/include/error.hpp index 64abf1a..4e13f54 100644 --- a/include/error.hpp +++ b/include/error.hpp @@ -78,9 +78,7 @@ namespace gdpm{ string get_message() const { return m_message; } bool has_occurred() const { return m_code != 0; } - bool operator()(){ - return has_occurred(); - } + bool operator()(){ return has_occurred(); } private: int m_code; @@ -89,14 +87,20 @@ namespace gdpm{ // Add logging function that can handle error objects namespace log { - template static constexpr void error(const gdpm::error& e){ -#if GDPM_LOG_LEVEL > 1 +#if GDPM_LOG_LEVEL > ERROR vlog( - fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}" GDPM_COLOR_LOG_RESET, e.get_message()), + fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), e.get_message()), fmt::make_format_args("" /*e.get_message()*/) ); #endif } + + template + static constexpr void error(const S& prefix, const gdpm::error& e){ + vlog( + fmt::format(GDPM_COLOR_LOG_ERROR + prefix + GDPM_COLOR_LOG_RESET, e.get_message()) + ); + } } } diff --git a/include/http.hpp b/include/http.hpp index 3e012f4..c7a11c5 100644 --- a/include/http.hpp +++ b/include/http.hpp @@ -6,9 +6,73 @@ namespace gdpm::http{ using headers_t = std::unordered_map; + + // REF: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status enum response_code{ - OK = 200, - NOT_FOUND = 400 + CONTINUE = 100, + SWITCHING_PROTOCOLS = 101, + PROCESSING = 102, + EARLY_HINTS = 103, + OK = 200, + CREATED = 201, + ACCEPTED = 202, + NON_AUTHORITATIVE_INFORMATION = 203, + NO_CONTENT = 204, + RESET_CONTENT = 205, + PARTIAL_CONTENT = 206, + MULTI_STATUS = 207, + ALREADY_REPORTED = 208, + IM_USED = 226, + MULTIPLE_CHOICES = 300, + MOVED_PERMANENTLY = 301, + FOUND = 302, + SEE_OTHER = 303, + NOT_MODIFIED = 304, + USE_PROXY = 305, + UNUSED = 306, + TEMPORARY_REDIRECT = 307, + PERMANENT_REDIRECT = 308, + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + PAYMENT_REQUIRED = 402, + FORBIDDEN = 403, + NOT_FOUND = 404, + METHOD_NOT_ALLOWED = 405, + NOT_ACCEPTABLE = 406, + PROXY_AUTHENTICATION_REQUIRED = 407, + REQUEST_TIMEOUT = 408, + CONFLICT = 409, + GONE = 410, + LENGTH_REQUIRED = 411, + PRECONDITION_FAILED = 412, + PAYLOAD_TOO_LARGE = 413, + URI_TOO_LONG = 414, + UNSUPPORTED_MEDIA_TYPE = 415, + RANGE_NOT_SATISFIABLE = 416, + EXPECTATION_FAILED = 417, + IM_A_TEAPOT = 418, + MISDIRECTED_REQUEST = 421, + UNPROCESSABLE_CONTENT = 422, + LOCKED = 423, + FAILED_DEPENDENCY = 424, + TOO_EARLY = 425, + UPGRADE_REQUIRED = 426, + PRECONDITION_REQUIRED = 428, + TOO_MANY_REQUESTS = 429, + REQUEST_HEADER_FILEDS_TOO_LARGE = 431, + UNAVAILABLE_FOR_LEGAL_REASONS = 451, + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502, + SERVICE_UNAVAILABLE = 503, + GATEWAY_TIMEOUT = 504, + HTTP_VERSION_NOT_SUPPORTED = 505, + VARIANT_ALSO_NEGOTIATES = 506, + INSUFFICIENT_STORAGE = 507, + LOOP_DETECTED = 508, + NOT_EXTENDED = 510, + NETWORK_AUTHENTICATION_REQUIRED = 511 + }; struct response{ long code = 0; @@ -18,14 +82,15 @@ namespace gdpm::http{ }; - struct params { + struct request_params { + headers_t headers = {}; size_t timeout = GDPM_CONFIG_TIMEOUT_MS; int verbose = 0; }; string url_escape(const string& url); - response request_get(const string& url, const http::params& params = http::params()); - response request_post(const string& url, const char *post_fields="", const http::params& params = http::params()); - response download_file(const string& url, const string& storage_path, const http::params& params = http::params()); + response request_get(const string& url, const http::request_params& params = http::request_params()); + response request_post(const string& url, const http::request_params& params = http::request_params()); + response download_file(const string& url, const string& storage_path, const http::request_params& params = http::request_params()); } \ No newline at end of file diff --git a/include/log.hpp b/include/log.hpp index cb2de48..2c12f22 100644 --- a/include/log.hpp +++ b/include/log.hpp @@ -3,6 +3,8 @@ #include "utils.hpp" #include "colors.hpp" +#include "types.hpp" +#include // #include #if __cplusplus > 201703L @@ -18,22 +20,21 @@ TODO: Write log information to file */ namespace gdpm::log { - template concept RequireMinArgs = requires (std::size_t min){ sizeof...(Args) > min; }; enum level{ - NONE, + NONE = 0, INFO, WARNING, DEBUG, ERROR }; - struct config { - static int level; - static std::string prefix; - static std::string path; - static bool print_to_stdout; - static bool print_to_stderr; + struct context { + int level; + string prefix; + string path; + bool print_to_stdout; + bool print_to_stderr; }; static void vlog(fmt::string_view format, fmt::format_args args){ @@ -46,7 +47,7 @@ namespace gdpm::log template static constexpr void info(const S& format, Args&&...args){ -#if GDPM_LOG_LEVEL > 0 +#if GDPM_LOG_LEVEL > NONE vlog( fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), // fmt::make_format_args(args...) @@ -55,18 +56,40 @@ namespace gdpm::log #endif } + template + static constexpr void info(const S& prefix, const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > INFO + vlog( + fmt::format(GDPM_COLOR_LOG_INFO + prefix + GDPM_COLOR_LOG_RESET, format), + fmt::make_format_args(args...) + ); +#endif + } + + template + static constexpr void info(const context& context, const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > INFO + vlog( + fmt::format(GDPM_COLOR_LOG_INFO + context.prefix + GDPM_COLOR_LOG_RESET, format), + fmt::make_format_args(args...) + ); +#endif + } + template static constexpr void info_n(const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > INFO vlog( fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), // fmt::make_format_args(args...) fmt::make_format_args(args...) ); +#endif } template static constexpr void error(const S& format, Args&&...args){ -#if GDPM_LOG_LEVEL > 1 +#if GDPM_LOG_LEVEL > ERROR vlog( fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), // fmt::make_format_args(args...) @@ -75,9 +98,29 @@ namespace gdpm::log #endif } + template + static constexpr void error(const S& prefix, const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > ERROR + vlog( + fmt::format(GDPM_COLOR_LOG_ERROR + prefix + GDPM_COLOR_LOG_RESET, format), + fmt::make_format_args(args...) + ); +#endif + } + + template + static constexpr void error(const context& context, const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > ERROR + vlog( + fmt::format(GDPM_COLOR_LOG_ERROR + context.prefix + GDPM_COLOR_LOG_RESET, format), + fmt::make_format_args(args...) + ); +#endif + } + template static constexpr void debug(const S& format, Args&&...args){ -#if GDPM_LOG_LEVEL > 1 +#if GDPM_LOG_LEVEL > DEBUG vlog( fmt::format(GDPM_COLOR_LOG_DEBUG "[DEBUG {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), // fmt::make_format_args(args...) @@ -86,6 +129,26 @@ namespace gdpm::log #endif } + template + static constexpr void debug(const S& prefix, const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > DEBUG + vlog( + fmt::format(GDPM_COLOR_LOG_DEBUG + prefix + GDPM_COLOR_LOG_RESET, format), + fmt::make_format_args(args...) + ); +#endif + } + + template + static constexpr void debug(const context& context, const S& format, Args&&...args){ +#if GDPM_LOG_LEVEL > DEBUG + vlog( + fmt::format(GDPM_COLOR_LOG_DEBUG + context.prefix + GDPM_COLOR_LOG_RESET, format), + fmt::make_format_args(args...) + ); +#endif + } + template static constexpr void print(const S& format, Args&&...args){ vlog( @@ -95,7 +158,7 @@ namespace gdpm::log ); } - template + template static constexpr void println(const S& format, Args&&...args){ vlog( fmt::format("{}\n", format), @@ -105,7 +168,7 @@ namespace gdpm::log } template - static constexpr void println(const std::string& format = ""){ + static constexpr void println(const string& format = ""){ println(format); } diff --git a/include/package.hpp b/include/package.hpp index 4568397..9cbb3bd 100644 --- a/include/package.hpp +++ b/include/package.hpp @@ -36,18 +36,18 @@ namespace gdpm::package { std::vector dependencies; }; + enum install_method_e : int{ + GLOBAL_LINK_LOCAL = 0, + GLOBAL_CLONE_LOCAL = 1, + GLOBAL_ONLY = 2, + LOCAL_ONLY = 3 + }; struct params { - enum install_method_e{ - GLOBAL_LINK_LOCAL, - GLOBAL_CLONE_LOCAL, - GLOBAL_ONLY, - LOCAL_ONLY - }; - bool parallel_jobs = 1; - bool use_cache = true; - bool use_remote = true; - bool skip_prompt = false; - std::string remote_source = ""; + int parallel_jobs = 1; + bool enable_cache = true; + bool enable_sync = true; + bool skip_prompt = false; + string remote_source = ""; install_method_e install_method = GLOBAL_LINK_LOCAL; }; @@ -87,7 +87,13 @@ namespace gdpm::package { */ GDPM_DLL_EXPORT error install(const config::context& config, const title_list& package_titles, const params& params = package::params()); - + /*! + @brief Adds package to project locally only. + @param config + @param package_titles + @param params + */ + GDPM_DLL_EXPORT error add(const config::context& config, const title_list& package_titles, const params& params = package::params()); /*! @brief Remove's package and contents from local database. */ @@ -106,6 +112,7 @@ namespace gdpm::package { GDPM_DLL_EXPORT result_t get_package_info(const opts_t& opts); GDPM_DLL_EXPORT result_t get_package_titles(const info_list& packages); GDPM_DLL_EXPORT void clean_temporary(const config::context& config, const title_list& package_titles); + GDPM_DLL_EXPORT params make_params(const var_args& args, const var_opts& opts); /* Dependency Management API */ GDPM_DLL_EXPORT result_t synchronize_database(const config::context& config, const title_list& package_titles); diff --git a/include/package_manager.hpp b/include/package_manager.hpp index 1ec1771..14ea6c4 100644 --- a/include/package_manager.hpp +++ b/include/package_manager.hpp @@ -16,7 +16,7 @@ #include namespace gdpm::package_manager { - extern remote::repository_map repo_sources; + extern remote::repository_map remote_sources; extern CURL *curl; extern CURLcode res; extern config::context config; @@ -27,12 +27,13 @@ namespace gdpm::package_manager { }; struct exec_args{ - args_t args; - opts_t opts; + var_args args; + var_opts opts; }; enum class action_e{ install, + add, remove, update, search, @@ -47,12 +48,12 @@ namespace gdpm::package_manager { none }; -GDPM_DLL_EXPORT result_t initialize(int argc, char **argv); - GDPM_DLL_EXPORT int execute(const args_t& args, const opts_t& opts); + GDPM_DLL_EXPORT result_t initialize(int argc, char **argv); + GDPM_DLL_EXPORT int execute(const exec_args& in); GDPM_DLL_EXPORT void finalize(); /* Auxiliary Functions */ GDPM_DLL_EXPORT cxxargs _parse_arguments(int argc, char **argv); GDPM_DLL_EXPORT result_t _handle_arguments(const cxxargs& args); - GDPM_DLL_EXPORT void run_command(action_e command, const package::title_list& package_titles, const opts_t& opts); + GDPM_DLL_EXPORT void run_command(action_e command, const var_args& args, const var_opts& opts); } \ No newline at end of file diff --git a/include/plugin.hpp b/include/plugin.hpp index e4441aa..734ea17 100644 --- a/include/plugin.hpp +++ b/include/plugin.hpp @@ -1,12 +1,12 @@ - - +#pragma once +#include "types.hpp" #include namespace gdpm::plugin{ struct info{ - std::string name; - std::string description; - std::string version; + string name; + string description; + string version; }; extern int init(int argc, char **argv); extern int set_name(const char *name); diff --git a/include/rest_api.hpp b/include/rest_api.hpp index 0bd8a83..575b586 100644 --- a/include/rest_api.hpp +++ b/include/rest_api.hpp @@ -25,8 +25,8 @@ namespace gdpm::rest_api{ constexpr const char *POST_AssetEditId = "/asset/edit/{id}"; } - bool register_account(const std::string& username, const std::string& password, const std::string& email); - bool login(const std::string& username, const std::string& password); + bool register_account(const string& username, const string& password, const string& email); + bool login(const string& username, const string& password); bool logout(); // bool change_password() @@ -38,9 +38,9 @@ namespace gdpm::rest_api{ type_e type; int category; support_e support; - std::string filter; - std::string user; - std::string godot_version; + string filter; + string user; + string godot_version; int max_results; int page; sort_e sort; @@ -51,21 +51,21 @@ namespace gdpm::rest_api{ context make_from_config(const config::context& config); context make_context(type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const std::string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE); - std::string to_string(type_e type); - std::string to_string(support_e support); - std::string to_string(sort_e sort); + string to_string(type_e type); + string to_string(support_e support); + string to_string(sort_e sort); void _print_params(const context& params); - rapidjson::Document _parse_json(const std::string& r, int verbose = 0); - std::string _prepare_request(const std::string& url, const context& context); + rapidjson::Document _parse_json(const string& r, int verbose = 0); + string _prepare_request(const string& url, const context& context); - bool register_account(const std::string& username, const std::string& password, const std::string& email); - bool login(const std::string& username, const std::string& password); + bool register_account(const string& username, const string& password, const string& email); + bool login(const string& username, const string& password); bool logout(); - rapidjson::Document configure(const std::string& url = constants::HostUrl, type_e type = any, int verbose = 0); - rapidjson::Document get_assets_list(const std::string& url = constants::HostUrl, type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const std::string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE); - rapidjson::Document get_assets_list(const std::string& url, const context& params = {}); - rapidjson::Document get_asset(const std::string& url, int asset_id, const context& params = {}); + rapidjson::Document configure(const string& url = constants::HostUrl, type_e type = any, int verbose = 0); + rapidjson::Document get_assets_list(const string& url = constants::HostUrl, type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE); + rapidjson::Document get_assets_list(const string& url, const context& params = {}); + rapidjson::Document get_asset(const string& url, int asset_id, const context& params = {}); bool delete_asset(int asset_id); // ...for moderators bool undelete_asset(int asset_id); // ...for moderators bool set_support_level(int asset_id); // ...for moderators @@ -81,11 +81,11 @@ namespace gdpm::rest_api{ void get_asset_edit(int asset_id); /* POST /asset/edit/{id}/review */ - std::string review_asset_edit(int asset_id); + string review_asset_edit(int asset_id); /* POST /asset/edit/{id}/accept */ - std::string accept_asset_edit(int asset_id); // ...for moderators + string accept_asset_edit(int asset_id); // ...for moderators /* POST /asset/edit/{id}/reject */ - std::string reject_asset_edit(int asset_id); + string reject_asset_edit(int asset_id); } \ No newline at end of file diff --git a/include/types.hpp b/include/types.hpp index bada267..3b1748a 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace gdpm{ class error; @@ -29,6 +30,16 @@ namespace gdpm{ non_movable(non_movable&&) = delete; }; + enum variant_type_index : int{ + INT = 0, + FLOAT = 1, + BOOL = 2, + STRING = 3, + STRING_LIST = 4, + STRING_MAP = 5, + SIZE_T = 6, + }; + template concept error_t = requires{ std::is_same::value; }; @@ -36,12 +47,58 @@ namespace gdpm{ using string_list = std::vector; using string_map = std::unordered_map; using string_pair = std::pair; - using var = std::variant; + using var = std::variant; template using _args_t = std::vector; using args_t = _args_t; - template > + using var_args = _args_t; + using var_list = std::vector; + using var_map = std::unordered_map; + template > using _opts_t = std::unordered_map; using opts_t = _opts_t; + using opt = std::pair; + using var_opt = std::pair; + using var_opts = _opts_t; + template + using _task_list = std::vector>; + using task_list = _task_list; + + inline string_list unwrap(const var_args& args){ + string_list sl; + std::for_each(args.begin(), args.end(), [&sl](const var& v){ + if(v.index() == STRING){ + sl.emplace_back(std::get(v)); + } + }); + return sl; + } + + inline opts_t unwrap(const var_opts& opts){ + opts_t o; + std::for_each(opts.begin(), opts.end(), [&o](const var_opt& opt){ + if(opt.second.index() == STRING){ + string_list sl; + string arg(std::get(opt.second)); // must make copy for const& + sl.emplace_back(arg); + o.insert(std::pair(opt.first, sl)); + } + }); + return o; + } + + template + constexpr void get(const var& v, T& target){ + switch(v.index()){ + case INT: /*return*/ target = std::get(v); + case FLOAT: /*return*/ target = std::get(v); + case BOOL: /*return*/ target = std::get(v); + case STRING: /*return*/ target = std::get(v); + case STRING_LIST: /*return*/ target = std::get(v); + case STRING_MAP: /*return*/ target = std::get(v); + case SIZE_T: /*return*/ target = std::get(v); + default: /*return*/ target = 0; + } + } } diff --git a/src/config.cpp b/src/config.cpp index 137bf41..defa812 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -130,12 +130,12 @@ namespace gdpm::config{ /* Must check if keys exists first, then populate `_config_params`. */ if(doc.HasMember("remote_sources")){ - if(doc["remote_sources"].IsArray()){ + if(doc["remote_sources"].IsObject()){ const Value& srcs = doc["remote_sources"]; for(auto& src : srcs.GetObject()){ // config.remote_sources.push_back(src.GetString()); config.remote_sources.insert( - std::pair(src.name.GetString(), src.value.GetString()) + string_pair(src.name.GetString(), src.value.GetString()) ); } } else { diff --git a/src/http.cpp b/src/http.cpp index 050ce9f..4899e0f 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -20,7 +20,7 @@ namespace gdpm::http{ response request_get( const string& url, - const http::params& params + const http::request_params& params ){ CURL *curl = nullptr; CURLcode res; @@ -58,8 +58,7 @@ namespace gdpm::http{ response request_post( const string& url, - const char *post_fields, - const http::params& params + const http::request_params& params ){ CURL *curl = nullptr; CURLcode res; @@ -70,13 +69,25 @@ namespace gdpm::http{ using namespace std::chrono_literals; utils::delay(); #endif + string h; + std::for_each( + params.headers.begin(), + params.headers.end(), + [&h](const string_pair& kv){ + h += kv.first + "=" + kv.second + "&"; + } + ); + h.pop_back(); + h = url_escape(h); + // const char *post_fields = ""; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if(curl){ curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=daniel&project=curl"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, h.size()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, h.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer); curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str()); @@ -98,7 +109,7 @@ namespace gdpm::http{ response download_file( const string& url, const string& storage_path, - const http::params& params + const http::request_params& params ){ CURL *curl = nullptr; CURLcode res; diff --git a/src/main.cpp b/src/main.cpp index f19746d..c53c9e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,9 +9,11 @@ int main(int argc, char **argv){ using namespace gdpm; - result_t r_input = package_manager::initialize(argc, argv); - package_manager::exec_args input = r_input.unwrap_unsafe(); - package_manager::execute(input.args, input.opts); - package_manager::finalize(); + using namespace gdpm::package_manager; + + result_t r_input = initialize(argc, argv); + exec_args input = r_input.unwrap_unsafe(); + execute(input); + finalize(); return 0; } \ No newline at end of file diff --git a/src/package.cpp b/src/package.cpp index d5fae0a..4890115 100644 --- a/src/package.cpp +++ b/src/package.cpp @@ -1,11 +1,13 @@ #include "package.hpp" #include "error.hpp" +#include "log.hpp" #include "rest_api.hpp" #include "config.hpp" #include "cache.hpp" #include "http.hpp" #include "remote.hpp" +#include "types.hpp" #include #include #include @@ -21,9 +23,16 @@ namespace gdpm::package{ /* TODO: Need a way to use remote sources from config until none left */ - /* Check if the package data is already stored in cache. If it is, there - is no need to do a lookup to synchronize the local database since we - have all the information we need to fetch the asset data. */ + /* + Implementation steps: + + 1. Synchronize the cache information by making a request using remote API. + (can skip with `--no-sync` options) + 2. Check if the package is installed. If it is, make sure it is latest version. + If not, download and update to the latest version. + 3. Extract package contents and copy/move to the correct install location. + */ + result_t result = cache::get_package_info_by_title(package_titles); package::info_list p_found = {}; package::info_list p_cache = result.unwrap_unsafe(); @@ -37,6 +46,8 @@ namespace gdpm::package{ } } + /* Match queried package titles with those found in cache. */ + log::debug("Searching for packages in cache..."); for(const auto& p_title : package_titles){ auto found = std::find_if( p_cache.begin(), @@ -50,16 +61,26 @@ namespace gdpm::package{ } } + /* If size of package_titles == p_found, then all packages can be installed + from cache and there's no need to query remote API. However, this will + only installed the latest *local* version and will not sync with remote. */ + if(p_found.size() == package_titles.size()){ + log::info("Found all packages stored in local cache."); + } + /* Found nothing to install so there's nothing to do at this point. */ if(p_found.empty()){ - constexpr const char *message = "No packages found to install."; - log::error(message); - return error(constants::error::NOT_FOUND, message); + error error( + constants::error::NOT_FOUND, + "No packages found to install." + ); + log::error(error); + return error; } log::println("Packages to install: "); for(const auto& p : p_found){ - std::string output((p.is_installed) ? p.title + " (reinstall)" : p.title); + string output((p.is_installed) ? p.title + " (reinstall)" : p.title); log::print(" {} ", (p.is_installed) ? p.title + " (reinstall)" : p.title); } log::println(""); @@ -70,9 +91,8 @@ namespace gdpm::package{ } /* Try and obtain all requested packages. */ - using ss_pair = std::pair; - std::vector dir_pairs; - std::vector> tasks; + std::vector dir_pairs; + task_list tasks; rest_api::context rest_api_params = rest_api::make_from_config(config); for(auto& p : p_found){ // TODO: Execute each in parallel using coroutines?? @@ -80,8 +100,8 @@ namespace gdpm::package{ in global storage location only. */ log::info("Fetching asset data for \"{}\"...", p.title); - std::string url{config.remote_sources.at(params.remote_source) + rest_api::endpoints::GET_AssetId}; - std::string package_dir, tmp_dir, tmp_zip; + string url{config.remote_sources.at(params.remote_source) + rest_api::endpoints::GET_AssetId}; + string package_dir, tmp_dir, tmp_zip; /* Retrieve necessary asset data if it was found already in cache */ Document doc; @@ -161,7 +181,7 @@ namespace gdpm::package{ } } - dir_pairs.emplace_back(ss_pair(tmp_zip, package_dir + "/")); + dir_pairs.emplace_back(string_pair(tmp_zip, package_dir + "/")); p.is_installed = true; p.install_path = package_dir; @@ -182,6 +202,16 @@ namespace gdpm::package{ } + error add( + const config::context& config, + const title_list& package_titles, + const params& params + ){ + + return error(); + } + + error remove( const config::context& config, const string_list& package_titles, @@ -346,11 +376,11 @@ namespace gdpm::package{ error search( const config::context& config, - const package::title_list &package_titles, + const package::title_list &package_titles, const package::params& params ){ result_t r_cache = cache::get_package_info_by_title(package_titles); - std::vector p_cache = r_cache.unwrap_unsafe(); + info_list p_cache = r_cache.unwrap_unsafe(); if(!p_cache.empty() && !config.enable_sync){ print_list(p_cache); @@ -393,22 +423,24 @@ namespace gdpm::package{ using namespace rapidjson; using namespace std::filesystem; - if(opts.empty() || opts.contains("packages")){ + string show((!args.empty()) ? args[0] : ""); + if(show.empty() || show == "packages"){ result_t r_installed = cache::get_installed_packages(); info_list p_installed = r_installed.unwrap_unsafe(); if(!p_installed.empty()){ - log::println("Installed packages: "); print_list(p_installed); + } + else{ + log::println("empty"); } } - else if(opts.contains("remote")){ + else if(show == "remote"){ remote::print_repositories(config); } else{ error error( constants::error::UNKNOWN_COMMAND, "Unrecognized subcommand. Try either 'packages' or 'remote' instead." - ); log::error(error); } @@ -654,6 +686,28 @@ namespace gdpm::package{ } + template + auto set_if_key_exists( + const var_opts& o, + const string& k, + T& p + ){ + if(o.count(k)){ p = std::get(o.at(k)); } + } + + params make_params(const var_args& args, const var_opts& opts){ + params p; + set_if_key_exists(opts, "jobs", p.parallel_jobs); + set_if_key_exists(opts, "cache", p.enable_cache); + set_if_key_exists(opts, "sync", p.enable_sync); + set_if_key_exists(opts, "skip-prompt", p.skip_prompt); + set_if_key_exists(opts, "remote-source", p.remote_source); + // set_if_key_exists(opts, "install-method", p.install_method); + + return p; + } + + result_t synchronize_database( const config::context& config, const title_list& package_titles diff --git a/src/package_manager.cpp b/src/package_manager.cpp index e63edc9..2e612a1 100644 --- a/src/package_manager.cpp +++ b/src/package_manager.cpp @@ -35,7 +35,7 @@ */ namespace gdpm::package_manager{ - remote::repository_map repo_sources; + remote::repository_map remote_sources; CURL *curl; CURLcode res; config::context config; @@ -76,8 +76,8 @@ namespace gdpm::package_manager{ } - int execute(const args_t& args, const opts_t& opts){ - run_command(action, packages, opts); + int execute(const exec_args& in){ + run_command(action, in.args, in.opts); if(clean_tmp_dir) package::clean_temporary(config, packages); return 0; @@ -102,7 +102,8 @@ namespace gdpm::package_manager{ options.allow_unrecognised_options(); options.custom_help("[COMMAND] [OPTIONS...]"); options.add_options("Command") - ("command", "Specify the input parameters", cxxopts::value()) + ("command", "Specify the input parameters", cxxopts::value()) + ("positional", "", cxxopts::value()) ("install", "Install package or packages.", cxxopts::value()->implicit_value(""), "") ("remove", "Remove a package or packages.", cxxopts::value()->implicit_value(""), "") ("update", "Update a package or packages. This will update all packages if no argument is provided.", cxxopts::value()->implicit_value(""), "") @@ -111,13 +112,14 @@ namespace gdpm::package_manager{ ("list", "Show list of installed packages.") ("link", "Create a symlink (or shortcut) to target directory. Must be used with the `--path` argument.", cxxopts::value(), "") ("clone", "Clone packages into target directory. Must be used with the `--path` argument.", cxxopts::value(), "") + ("cache", "Caching operations", cxxopts::value()) ("clean", "Clean temporary downloaded files.") ("fetch", "Fetch asset data from remote sources.") ("remote", "Set a source repository.", cxxopts::value()->default_value(constants::AssetRepo), "") ("h,help", "Print this message and exit.") ("version", "Show the current version and exit.") ; - options.parse_positional({"command"}); + options.parse_positional({"command", "positional"}); options.positional_help(""); options.add_options("Other") ("c,config", "Set the config file path.", cxxopts::value()) @@ -128,9 +130,8 @@ namespace gdpm::package_manager{ ("support", "Set the support level for API (all|official|community|testing).") ("max-results", "Set the max results to return from search.", cxxopts::value()->default_value("500"), "") ("godot-version", "Set the Godot version to include in request.", cxxopts::value()) - ("set-priority", "Set the priority for remote source. Lower values are used first (0...100).", cxxopts::value()) - ("set-packages-directory", "Set the local package storage location.", cxxopts::value()) - ("set-temporary-directory", "Set the local temporary storage location.", cxxopts::value()) + ("package-dir", "Set the local package storage location.", cxxopts::value()) + ("tmp-dir", "Set the local temporary storage location.", cxxopts::value()) ("timeout", "Set the amount of time to wait for a response.", cxxopts::value()) ("no-sync", "Disable synchronizing with remote.", cxxopts::value()->implicit_value("true")->default_value("false")) ("y,no-prompt", "Bypass yes/no prompt for installing or removing packages.") @@ -142,17 +143,38 @@ namespace gdpm::package_manager{ } + template + void insert( + var_opts& opts, + const cxxopts::ParseResult& result, + const string& key + ){ + opts.insert(var_opt(key, result[key].as())); + }; + + result_t _handle_arguments(const cxxargs& args){ const auto& result = args.result; const auto& options = args.options; exec_args in; + + // auto get_opt = [](const string& key){ + // result[key].as(); + // } + // auto get_opt = [&result](const string& key){ + // return opt(key, result[key].as()); + // }; /* Set option variables first to be used in functions below. */ + if(result.count("search")){ + in.args = result["search"].as(); + } if(result.count("help")){ log::println("{}", options.help()); } if(result.count("config")){ config.path = result["config"].as(); + insert(in.opts, result, "config"); config::load(config.path, config); log::info("Config: {}", config.path); } @@ -163,9 +185,8 @@ namespace gdpm::package_manager{ string argv = result.arguments_string(); log::println("argv: {}", argv); remote::add_repositories(config, {}); - } - else if(sub_command == "delete"){ + else if(sub_command == "remove"){ remote::remove_respositories(config, {}); log::println("argv: {}"); } @@ -174,9 +195,10 @@ namespace gdpm::package_manager{ string path = result["file"].as(); string contents = utils::readfile(path); packages = utils::parse_lines(contents); + insert(in.opts, result, "file"); } if(result.count("path")){ - in.opts.insert({"path", result["path"].as()}); + insert(in.opts, result, "path"); } if(result.count("sort")){ string r = result["sort"].as(); @@ -187,14 +209,16 @@ namespace gdpm::package_manager{ else if(r == "name") sort = rest_api::sort_e::name; else if(r == "updated") sort = rest_api::sort_e::updated; api_params.sort = sort; + in.opts.insert(var_opt("sort", r)); } if(result.count("type")){ - string r = result["type"].as(); + string r = result["type"].as(); rest_api::type_e type = rest_api::type_e::any; if(r == "any") type = rest_api::type_e::any; else if(r == "addon") type = rest_api::type_e::addon; else if(r == "project") type = rest_api::type_e::project; api_params.type = type; + in.opts.insert(var_opt("type", r)); } if(result.count("support")){ string r = result["support"].as(); @@ -204,42 +228,52 @@ namespace gdpm::package_manager{ else if(r == "community") support = rest_api::support_e::community; else if(r == "testing") support = rest_api::support_e::testing; api_params.support = support; + in.opts.insert(var_opt("support", r)); } if(result.count("max-results")){ api_params.max_results = result["max-results"].as(); + insert(in.opts, result, "max-results"); } if(result.count("godot-version")){ config.godot_version = result["godot-version"].as(); + insert(in.opts, result, "godot-version"); } if(result.count("timeout")){ config.timeout = result["timeout"].as(); + insert(in.opts, result, "timeout"); } if(result.count("no-sync")){ config.enable_sync = false; + in.opts.insert(var_opt("sync", "disabled")); } - if(result.count("set-priority")){ - priority = result["set-priority"].as(); + if(result.count("package-dir")){ + config.packages_dir = result["package-dir"].as(); + insert(in.opts, result, "package-dir"); } - if(result.count("set-packages-directory")){ - config.packages_dir = result["set-packages-directory"].as(); - } - if(result.count("set-temporary-directory")){ - config.tmp_dir = result["set-temporary-directory"].as(); + if(result.count("tmp-dir")){ + config.tmp_dir = result["tmp-dir"].as(); + insert(in.opts, result, "tmp-dir"); } if(result.count("yes")){ skip_prompt = true; + in.opts.insert(opt("skip-prompt", true)); } if(result.count("link")){ packages = result["link"].as(); + insert(in.opts, result, "link"); } if(result.count("clone")){ packages = result["clone"].as(); + insert(in.opts, result, "clone"); } if(result.count("clean")){ + in.opts.insert(opt("clean", true)); clean_tmp_dir = true; } config.verbose = 0; config.verbose += result["verbose"].as(); + insert(in.opts, result, "verbose"); + string json = to_json(config); if(config.verbose > 0){ log::println("Verbose set to level {}", config.verbose); @@ -251,17 +285,20 @@ namespace gdpm::package_manager{ return result_t(in, error()); } - args_t _argv = result["command"].as(); - string sub_command = _argv[0]; - args_t argv{_argv.begin()+1, _argv.end()}; - if(packages.empty() && in.opts.size() > 0){ - for(const auto& arg : argv){ - packages.emplace_back(arg); + string sub_command = result["command"].as(); + if(result.count("positional")){ + string_list _argv = result["positional"].as(); + args_t argv{_argv.begin(), _argv.end()}; + if(!argv.empty()){ + for(const auto& arg : argv){ + in.args.emplace_back(arg); + } } } /* Catch arguments passed with dashes */ if(sub_command == "install") action = action_e::install; + else if(sub_command == "add") action = action_e::add; else if(sub_command == "remove") action = action_e::remove; else if(sub_command == "update") action = action_e::update; else if(sub_command == "search") action = action_e::search; @@ -272,7 +309,7 @@ namespace gdpm::package_manager{ else if(sub_command == "clean") action = action_e::clean; else if(sub_command == "sync") action = action_e::sync; else if(sub_command == "remote") action = action_e::remote; - else if(sub_command == "help" || argv[0] == "-h" || argv[0] == "--help" || argv[0].empty()){ + else if(sub_command == "help"){ action = action_e::help; log::println("{}", options.help()); } else{ @@ -283,22 +320,24 @@ namespace gdpm::package_manager{ /* Used to run the command AFTER parsing and setting all command line args. */ - void run_command(action_e c, const args_t& args, const opts_t& opts){ - package::params params; + void run_command(action_e c, const var_args& args, const var_opts& opts){ + package::params params = package::make_params(args, opts); + string_list args_list = unwrap(args); + opts_t opts_list = unwrap(opts); params.skip_prompt = skip_prompt; switch(c){ - case action_e::install: package::install(config, args, params); break; - case action_e::remove: package::remove(config, args, params); break; - case action_e::update: package::update(config, args, params); break; - case action_e::search: package::search(config, args, params); break; - case action_e::p_export: package::export_to(args); break; - case action_e::list: package::list(config, args, opts); break; - /* ...opts are the paths here */ - case action_e::link: package::link(config, args, opts); break; - case action_e::clone: package::clone(config, args, opts); break; - case action_e::clean: package::clean_temporary(config, args); break; - case action_e::sync: package::synchronize_database(config, args); break; - case action_e::remote: remote::_handle_remote(config, args, opts); break; + case action_e::install: package::install(config, args_list, params); break; + case action_e::remove: package::remove(config, args_list, params); break; + case action_e::update: package::update(config, args_list, params); break; + case action_e::search: package::search(config, args_list, params); break; + case action_e::p_export: package::export_to(args_list); break; + case action_e::list: package::list(config, args_list, opts_list); break; + /* ...opts are the paths here */ + case action_e::link: package::link(config, args_list, opts_list); break; + case action_e::clone: package::clone(config, args_list, opts_list); break; + case action_e::clean: package::clean_temporary(config, args_list); break; + case action_e::sync: package::synchronize_database(config, args_list); break; + case action_e::remote: remote::_handle_remote(config, args_list, opts_list); break; case action_e::help: /* ...runs in handle_arguments() */ break; case action_e::none: /* ...here to run with no command */ break; } diff --git a/src/rest_api.cpp b/src/rest_api.cpp index ac3cdb4..0a9623f 100644 --- a/src/rest_api.cpp +++ b/src/rest_api.cpp @@ -77,7 +77,7 @@ namespace gdpm::rest_api{ } string to_string(type_e type){ - std::string _s{"type="}; + string _s{"type="}; switch(type){ case any: _s += "any"; break; case addon: _s += "addon"; break;