diff --git a/README.md b/README.md index 12625ae..9a9c4a2 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The project uses the CMake or Meson build system and has been tested with GCC an * doctest (optional; for tests, but still WIP) -* cxxopts (header only) +* clipp (header only) * SQLite 3 diff --git a/include/colors.hpp b/include/colors.hpp index 22b3998..8344de1 100644 --- a/include/colors.hpp +++ b/include/colors.hpp @@ -1,3 +1,7 @@ +#pragma once + +#include "colors.hpp" +#include #if GDPM_ENABLE_COLORS == 1 #define GDPM_COLOR_BLACK "\033[0;30m" @@ -16,24 +20,26 @@ #define GDPM_COLOR_LIGHT_PURPLE "\033[0;35m" #define GDPM_COLOR_YELLOW "\033[0;33m" #define GDPM_COLOR_WHITE "\033[0;37m" + #define GDPM_COLOR_RESET GDPM_COLOR_WHITE #else - #define GDPM_COLOR_BLACK - #define GDPM_COLOR_BLUE - #define GDPM_COLOR_GREEN - #define GDPM_COLOR_CYAN - #define GDPM_COLOR_RED - #define GDPM_COLOR_PURPLE - #define GDPM_COLOR_BROWN - #define GDPM_COLOR_GRAY - #define GDPM_COLOR_DARK_GRAY - #define GDPM_COLOR_LIGHT_BLUE - #define GDPM_COLOR_LIGHT_GREEN - #define GDPM_COLOR_LIGHT_CYAN - #define GDPM_COLOR_LIGHT_RED - #define GDPM_COLOR_LIGHT_PURPLE - #define GDPM_COLOR_YELLOW - #define GDPM_COLOR_WHITE + #define GDPM_COLOR_BLACK "" + #define GDPM_COLOR_BLUE "" + #define GDPM_COLOR_GREEN "" + #define GDPM_COLOR_CYAN "" + #define GDPM_COLOR_RED "" + #define GDPM_COLOR_PURPLE "" + #define GDPM_COLOR_BROWN "" + #define GDPM_COLOR_GRAY "" + #define GDPM_COLOR_DARK_GRAY "" + #define GDPM_COLOR_LIGHT_BLUE "" + #define GDPM_COLOR_LIGHT_GREEN "" + #define GDPM_COLOR_LIGHT_CYAN "" + #define GDPM_COLOR_LIGHT_RED "" + #define GDPM_COLOR_LIGHT_PURPLE "" + #define GDPM_COLOR_YELLOW "" + #define GDPM_COLOR_WHITE "" + #define GDPM_COLOR_RESET GDPM_COLOR_WHITE #endif @@ -42,4 +48,24 @@ #define GDPM_COLOR_LOG_INFO GDPM_COLOR_LOG_RESET #define GDPM_COLOR_LOG_ERROR GDPM_COLOR_RED #define GDPM_COLOR_LOG_DEBUG GDPM_COLOR_YELLOW -#define GDPM_COLOR_LOG_WARNING GDPM_COLOR_YELLOW \ No newline at end of file +#define GDPM_COLOR_LOG_WARNING GDPM_COLOR_YELLOW + +namespace gdpm::color{ + inline std::string from_string(const std::string& color_name){ + if (color_name == "red"){ return GDPM_COLOR_RED; } + else if (color_name == "yellow"){ return GDPM_COLOR_YELLOW; } + else if (color_name == "green"){ return GDPM_COLOR_GREEN; } + else if (color_name == "blue"){ return GDPM_COLOR_BLUE; } + else if (color_name == "brown"){ return GDPM_COLOR_BROWN; } + else if (color_name == "gray"){ return GDPM_COLOR_GRAY; } + else if (color_name == "black"){ return GDPM_COLOR_BLACK; } + else if (color_name == "purple"){ return GDPM_COLOR_PURPLE; } + else if (color_name == "gray"){ return GDPM_COLOR_DARK_GRAY; } + else if (color_name == "light-blue"){ return GDPM_COLOR_LIGHT_BLUE; } + else if (color_name == "light-green"){ return GDPM_COLOR_LIGHT_GREEN; } + else if (color_name == "light-cyan"){ return GDPM_COLOR_LIGHT_CYAN; } + else if (color_name == "light-red"){ return GDPM_COLOR_LIGHT_RED; } + else if (color_name == "light-purple"){ return GDPM_COLOR_LIGHT_PURPLE; } + return ""; + } +} \ No newline at end of file diff --git a/include/config.hpp b/include/config.hpp index 144db96..c249e0d 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -43,7 +43,8 @@ namespace gdpm::config{ error handle_config(config::context& config, const args_t& args, const var_opts& opts); 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); - void print(const context& config); + void print_json(const context& config); + void print_properties(const context& config, const string_list& properties); extern context config; } \ No newline at end of file diff --git a/include/constants.hpp b/include/constants.hpp index aa750ee..c817a8c 100644 --- a/include/constants.hpp +++ b/include/constants.hpp @@ -15,6 +15,7 @@ namespace gdpm::constants{ const std::string UserAgent("libcurl-agent/1.0 via GDPM (https://github.com/davidallendj/gdpm)"); const std::string AssetRepo("https://godotengine.org/asset-library/api/asset"); const std::string HostUrl("https://godotengine.org/asset-library/api"); + constexpr std::string WHITESPACE = " \n\r\t\f\v"; } /* Define default macros to set when building with -DGPM_* */ diff --git a/include/error.hpp b/include/error.hpp index 1a21b7a..847dd12 100644 --- a/include/error.hpp +++ b/include/error.hpp @@ -29,8 +29,10 @@ namespace gdpm::constants::error{ INVALID_ARG_COUNT, INVALID_CONFIG, INVALID_KEY, - HTTP_RESPONSE_ERROR, - STD_ERROR + HTTP_RESPONSE_ERR, + SQLITE_ERR, + JSON_ERR, + STD_ERR }; const string_list messages { diff --git a/include/log.hpp b/include/log.hpp index 2c12f22..ac76329 100644 --- a/include/log.hpp +++ b/include/log.hpp @@ -1,10 +1,12 @@ #pragma once +#include "clipp.h" #include "utils.hpp" #include "colors.hpp" #include "types.hpp" #include +#include // #include #if __cplusplus > 201703L @@ -21,22 +23,51 @@ TODO: Write log information to file namespace gdpm::log { - enum level{ - NONE = 0, - INFO, - WARNING, - DEBUG, - ERROR + enum level_e : int{ + NONE = 0, + INFO = 1, + WARNING = 2, + DEBUG = 3, + ERROR = 4 }; - struct context { - int level; - string prefix; - string path; - bool print_to_stdout; - bool print_to_stderr; + enum flag_opt { + PRINT_STDOUT = 0b00000001, + PRINT_STDERR = 0b00000010 }; + static int level = INFO; + static string prefix = ""; + static string suffix = ""; + static string path = ""; + static std::bitset<8> flags = PRINT_STDOUT | PRINT_STDERR; + static bool print_to_stdout; + static bool print_to_stderr; + + inline constexpr level_e to_level(int l){ + return static_cast(l); + } + + inline constexpr int to_int(const level_e& l){ + return static_cast(l); + } + + inline constexpr void set_flag(uint8_t flag, bool value){ + (value) ? flags.set(flag) : flags.reset(flag); + } + + inline constexpr bool get_flag(uint8_t flag){ + return flags.test(flag); + } + + inline constexpr void set_prefix_if(const std::string& v, bool predicate = !prefix.empty()){ + prefix = (predicate) ? v : prefix; + } + + inline constexpr void set_suffix_if(const std::string& v, bool predicate = !suffix.empty()){ + suffix = (predicate) ? v : suffix; + } + static void vlog(fmt::string_view format, fmt::format_args args){ fmt::vprint(format, args); } @@ -47,30 +78,13 @@ namespace gdpm::log template static constexpr void info(const S& format, Args&&...args){ + if(log::level < to_int(log::INFO)) + return; #if GDPM_LOG_LEVEL > NONE + set_prefix_if(fmt::format("[INFO {}] ", utils::timestamp())); + set_suffix_if("\n"); vlog( - fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), - // fmt::make_format_args(args...) - fmt::make_format_args(args...) - ); -#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::format(GDPM_COLOR_LOG_INFO "{}{}{}" GDPM_COLOR_LOG_RESET, prefix, format, suffix), fmt::make_format_args(args...) ); #endif @@ -78,10 +92,13 @@ namespace gdpm::log template static constexpr void info_n(const S& format, Args&&...args){ + if(log::level < to_int(log::INFO)) + return; #if GDPM_LOG_LEVEL > INFO + set_prefix_if(fmt::format("[INFO {}] ", utils::timestamp())); + set_suffix_if(""); vlog( - fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), - // fmt::make_format_args(args...) + fmt::format(GDPM_COLOR_LOG_INFO "{}{}{}" GDPM_COLOR_LOG_RESET, prefix, format, suffix), fmt::make_format_args(args...) ); #endif @@ -89,30 +106,13 @@ namespace gdpm::log template static constexpr void error(const S& format, Args&&...args){ + if(log::level < to_int(log::ERROR)) + return; #if GDPM_LOG_LEVEL > ERROR + set_prefix_if(std::format("[ERROR {}] ", utils::timestamp())); + set_suffix_if("\n"); vlog( - fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}] {}\n" 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& 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::format(GDPM_COLOR_LOG_ERROR "{}{}{}" GDPM_COLOR_LOG_RESET, prefix, format, suffix), fmt::make_format_args(args...) ); #endif @@ -120,30 +120,13 @@ namespace gdpm::log template static constexpr void debug(const S& format, Args&&...args){ + if(log::level < to_int(log::DEBUG)) + return; #if GDPM_LOG_LEVEL > DEBUG + set_prefix_if(std::format("[DEBUG {}] ", utils::timestamp())); + set_suffix_if("\n"); vlog( - fmt::format(GDPM_COLOR_LOG_DEBUG "[DEBUG {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), - // fmt::make_format_args(args...) - fmt::make_format_args(args...) - ); -#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::format(GDPM_COLOR_LOG_DEBUG "{}{}{}" GDPM_COLOR_LOG_RESET, prefix, format, suffix), fmt::make_format_args(args...) ); #endif @@ -153,7 +136,6 @@ namespace gdpm::log static constexpr void print(const S& format, Args&&...args){ vlog( fmt::format("{}", format), - // fmt::make_format_args(args...) fmt::make_format_args(args...) ); } @@ -162,7 +144,6 @@ namespace gdpm::log static constexpr void println(const S& format, Args&&...args){ vlog( fmt::format("{}\n", format), - // fmt::make_format_args(args...) fmt::make_format_args(args...) ); } diff --git a/include/package.hpp b/include/package.hpp index 050666b..f5790b5 100644 --- a/include/package.hpp +++ b/include/package.hpp @@ -8,6 +8,7 @@ #include "rest_api.hpp" #include #include +#include #include #include #include @@ -48,11 +49,11 @@ namespace gdpm::package { }; struct params { - args_t sub_commands; + args_t args; var_opts opts; string_list paths; string_list input_files; - string remote_source = "origin"; + string remote_source = "origin"; install_method_e install_method = GLOBAL_LINK_LOCAL; }; @@ -61,6 +62,7 @@ namespace gdpm::package { using id_list = std::vector; using path = std::string; using path_list = std::vector; + using path_refs = std::vector>; /*! @brief Install a Godot package from the Asset Library in the current project. diff --git a/include/package_manager.hpp b/include/package_manager.hpp index 28ec0e3..a552390 100644 --- a/include/package_manager.hpp +++ b/include/package_manager.hpp @@ -16,7 +16,6 @@ #include namespace gdpm::package_manager { - extern remote::repository_map remote_sources; extern CURL *curl; extern CURLcode res; extern config::context config; @@ -32,10 +31,13 @@ namespace gdpm::package_manager { link, clone, clean, - config, + config_get, + config_set, fetch, sync, - remote, + remote_add, + remote_remove, + remote_list, ui, help, none diff --git a/include/plugin.hpp b/include/plugin.hpp index 734ea17..f6b53e9 100644 --- a/include/plugin.hpp +++ b/include/plugin.hpp @@ -1,6 +1,7 @@ #pragma once #include "types.hpp" #include +#include namespace gdpm::plugin{ struct info{ @@ -8,9 +9,11 @@ namespace gdpm::plugin{ string description; string version; }; - extern int init(int argc, char **argv); - extern int set_name(const char *name); - extern int set_description(const char *description); - extern int set_version(const char *version); - extern int finalize(); + extern error initialize(int argc, char **argv); + extern error set_name(const char *name); + extern error set_description(const char *description); + extern error set_version(const char *version); + extern error finalize(); + + error load(std::filesystem::path path); } \ No newline at end of file diff --git a/include/remote.hpp b/include/remote.hpp index 0c6079d..3ab7c0e 100644 --- a/include/remote.hpp +++ b/include/remote.hpp @@ -8,14 +8,8 @@ #include "config.hpp" namespace gdpm::remote{ - using repo_names = string_list; - using repo_urls = string_list; - using repository_map = string_map; - - GDPM_DLL_EXPORT error handle_remote(config::context& config, const args_t& args, const var_opts& opts); - GDPM_DLL_EXPORT void set_repositories(config::context& context, const repository_map& repos); - GDPM_DLL_EXPORT void add_repositories(config::context& context, const repository_map& repos); - GDPM_DLL_EXPORT void remove_respositories(config::context& context, const repo_names& names); - GDPM_DLL_EXPORT void move_repository(config::context& context, int old_position, int new_position); - GDPM_DLL_EXPORT void print_repositories(const config::context& context); + GDPM_DLL_EXPORT error add_repository(config::context& config, const args_t& args); + GDPM_DLL_EXPORT error remove_respositories(config::context& config, const args_t& names); + GDPM_DLL_EXPORT void move_repository(config::context& config, int old_position, int new_position); + GDPM_DLL_EXPORT void print_repositories(const config::context& config); } \ No newline at end of file diff --git a/include/types.hpp b/include/types.hpp index e33728c..c604c8c 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -101,5 +101,4 @@ namespace gdpm{ default: /*return*/ target = 0; } } - -} +} \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp index 3a7f517..838f5ca 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -96,7 +96,12 @@ namespace gdpm::utils { } std::string readfile(const std::string& path); - void to_lower(std::string& s); + std::string to_lower(const std::string& s); + std::string trim(const std::string& s); + std::string trim_left(const std::string& s); + std::string trim_left(const std::string& s, const std::string& ref); + std::string trim_right(const std::string& s); + std::string trim_right(const std::string& s, const std::string& ref); std::vector parse_lines(const std::string& s); std::string replace_first(std::string& s, const std::string& from, const std::string& to); std::string replace_all(std::string& s, const std::string& from, const std::string& to); @@ -105,7 +110,7 @@ namespace gdpm::utils { bool prompt_user_yn(const char *message); void delay(std::chrono::milliseconds milliseconds = GDPM_REQUEST_DELAY); std::string join(const std::vector& target, const std::string& delimiter = ", "); - + std::string join(const std::unordered_map& target, const std::string& prefix = "", const std::string& delimiter = "\n"); // TODO: Add function to get size of decompressed zip namespace json { diff --git a/src/README.md b/src/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/src/cache.cpp b/src/cache.cpp index 73fa7b0..45b8f1a 100644 --- a/src/cache.cpp +++ b/src/cache.cpp @@ -1,5 +1,6 @@ #include "cache.hpp" +#include "error.hpp" #include "log.hpp" #include "constants.hpp" #include "package.hpp" @@ -12,7 +13,10 @@ namespace gdpm::cache{ - error create_package_database(bool overwrite, const params& params){ + error create_package_database( + bool overwrite, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg; @@ -77,7 +81,10 @@ namespace gdpm::cache{ } - error insert_package_info(const package::info_list& packages, const params& params){ + error insert_package_info( + const package::info_list& packages, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg = nullptr; @@ -115,7 +122,10 @@ namespace gdpm::cache{ } - result_t get_package_info_by_id(const package::id_list& package_ids, const params& params){ + result_t get_package_info_by_id( + const package::id_list& package_ids, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg = nullptr; @@ -179,7 +189,10 @@ namespace gdpm::cache{ } - result_t get_package_info_by_title(const package::title_list& package_titles, const params& params){ + result_t get_package_info_by_title( + const package::title_list& package_titles, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg = nullptr; @@ -306,17 +319,20 @@ namespace gdpm::cache{ } - error update_package_info(const package::info_list& packages, const params& params){ + error update_package_info( + const package::info_list& packages, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg = nullptr; int rc = sqlite3_open(params.cache_path.c_str(), &db); if(rc != SQLITE_OK){ - error error(rc, std::format( + error error( + constants::error::SQLITE_ERR, std::format( "update_package_info.sqlite3_open(): {}", sqlite3_errmsg(db) )); - log::error(error); sqlite3_close(db); return error; } @@ -360,7 +376,10 @@ namespace gdpm::cache{ } - error delete_packages(const package::title_list& package_titles, const params& params){ + error delete_packages( + const package::title_list& package_titles, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg = nullptr; @@ -395,7 +414,10 @@ namespace gdpm::cache{ } - error delete_packages(const package::id_list& package_ids, const params& params){ + error delete_packages( + const package::id_list& package_ids, + const params& params + ){ sqlite3 *db; sqlite3_stmt *res; char *errmsg = nullptr; diff --git a/src/config.cpp b/src/config.cpp index 0e3a323..dacaacb 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -37,8 +37,6 @@ namespace gdpm::config{ const context& config, bool pretty_print ){ - - /* Build a JSON string to pass to document */ string prefix = (pretty_print) ? "\n\t" : ""; string spaces = (pretty_print) ? " " : ""; @@ -232,6 +230,7 @@ namespace gdpm::config{ return error(); } + context make_context( const string& username, const string& password, @@ -286,8 +285,60 @@ namespace gdpm::config{ return error; } - void print(const context& config){ + void print_json(const context& config){ log::println("{}", to_json(config, true)); } + void _print_property( + const context& config, + const string& property + ){ + if(property.empty()) return; + else if(property == "username") log::println("username: {}", config.username); + else if(property == "password") log::println("password: {}", config.password); + else if(property == "path") log::println("path: {}", config.path); + else if(property == "token") log::println("token: {}", config.token); + else if(property == "packages_dir") log::println("package directory: {}", config.packages_dir); + else if(property == "tmp_dir") log::println("temporary directory: {}", config.tmp_dir); + else if(property == "remote_sources") log::println("remote sources: \n{}", utils::join(config.remote_sources, "\t", "\n")); + else if(property == "jobs") log::println("parallel jobs: {}", config.jobs); + else if(property == "timeout") log::println("timeout: {}", config.timeout); + else if(property == "sync") log::println("enable sync: {}", config.enable_sync); + else if(property == "cache") log::println("enable cache: {}", config.enable_cache); + else if(property == "prompt") log::println("skip prompt: {}", config.skip_prompt); + else if(property == "logging") log::println("enable file logging: {}", config.enable_file_logging); + else if(property == "clean") log::println("clean temporary files: {}", config.clean_temporary); + else if(property == "verbose") log::println("verbose: {}", config.verbose); + } + + void print_properties( + const context& config, + const string_list& properties + ){ + if(properties.empty()){ + _print_property(config, "username"); + _print_property(config, "password"); + _print_property(config, "path"); + _print_property(config, "token"); + _print_property(config, "packages_dir"); + _print_property(config, "tmp_dir"); + _print_property(config, "remote_sources"); + _print_property(config, "jobs"); + _print_property(config, "timeout"); + _print_property(config, "sync"); + _print_property(config, "cache"); + _print_property(config, "prompt"); + _print_property(config, "logging"); + _print_property(config, "clean"); + _print_property(config, "verbose"); + } + std::for_each( + properties.begin(), + properties.end(), + [&config](const string& property){ + _print_property(config, property); + } + ); + } + } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b056f00..5b73e29 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "config.hpp" #include "package_manager.hpp" #include "result.hpp" +#include int main(int argc, char **argv){ @@ -13,6 +14,7 @@ int main(int argc, char **argv){ error error = initialize(argc, argv); parse_arguments(argc, argv); - finalize(); - return 0; + finalize(); + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/package.cpp b/src/package.cpp index 51d7236..89dd36f 100644 --- a/src/package.cpp +++ b/src/package.cpp @@ -8,6 +8,8 @@ #include "http.hpp" #include "remote.hpp" #include "types.hpp" +#include "utils.hpp" +#include #include #include #include @@ -31,6 +33,7 @@ namespace gdpm::package{ 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); @@ -167,7 +170,7 @@ namespace gdpm::package{ log::println("Done."); }else{ error error( - constants::error::HTTP_RESPONSE_ERROR, + constants::error::HTTP_RESPONSE_ERR, std::format("HTTP Error: {}", response.code) ); log::error(error); @@ -186,7 +189,12 @@ namespace gdpm::package{ /* Update the cache data with information from */ log::info_n("Updating local asset data..."); - cache::update_package_info(p_found); + error error = cache::update_package_info(p_found); + if(error()){ + log::error(error); + return error; + } + log::println("done."); // }) // ); @@ -201,7 +209,8 @@ namespace gdpm::package{ const title_list& package_titles, const params& params ){ - + /* Install packages in local project instead of package database. + This will not cache the package information in the cache database. */ return error(); } @@ -289,7 +298,13 @@ namespace gdpm::package{ } log::println("Done."); log::info_n("Updating local asset data..."); - cache::update_package_info(p_cache); + { + error error = cache::update_package_info(p_cache); + if(error.has_occurred()){ + log::error(error); + return error; + } + } log::println("done."); return error(); @@ -402,7 +417,7 @@ namespace gdpm::package{ return error; } - log::info("{} package(s) found...", doc["total_items"].GetInt()); + // log::info("{} package(s) found...", doc["total_items"].GetInt()); print_list(doc); } return error(); @@ -416,16 +431,13 @@ namespace gdpm::package{ using namespace rapidjson; using namespace std::filesystem; - string show((!params.sub_commands.empty()) ? params.sub_commands[0] : ""); + string show((!params.args.empty()) ? params.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()){ print_list(p_installed); } - else{ - log::println("empty"); - } } else if(show == "remote"){ remote::print_repositories(config); @@ -476,30 +488,27 @@ namespace gdpm::package{ error link( const config::context& config, const title_list& package_titles, - const package::params& params + const package::params& params /* path is last arg */ ){ using namespace std::filesystem; - path_list paths = {}; - if(params.opts.contains("path")){ - paths = get(params.opts.at("path")); - } - - if(paths.empty()){ + if(params.args.empty()){ error error( - constants::error::PATH_NOT_DEFINED, - "No path set. Use '--path' option to set a path." + constants::error::INVALID_ARG_COUNT, + "Must supply at least 2 arguments (package name and path)" ); log::error(error); return error; } + + /* Check for packages in cache to link */ result_t r_cache = cache::get_package_info_by_title(package_titles); info_list p_found = {}; info_list p_cache = r_cache.unwrap_unsafe(); if(p_cache.empty()){ error error( constants::error::NOT_FOUND, - "Could not find any packages to link." + "Could not find any packages to link in cache." ); log::error(error); return error; @@ -522,20 +531,22 @@ namespace gdpm::package{ } /* Get the storage paths for all packages to create symlinks */ + path_refs paths = path_refs({params.args.back()}); const path package_dir{config.packages_dir}; for(const auto& p : p_found){ for(const auto& path : paths){ - log::info_n("Creating symlink for \"{}\" package to '{}'...", p.title, path + "/" + p.title); + const string _path = path; + log::info_n("link: \"{}\" -> '{}'...", p.title, _path + "/" + p.title); // std::filesystem::path target{config.packages_dir + "/" + p.title}; std::filesystem::path target = {current_path().string() + "/" + config.packages_dir + "/" + p.title}; - std::filesystem::path symlink_path{path + "/" + p.title}; + std::filesystem::path symlink_path{_path + "/" + p.title}; if(!std::filesystem::exists(symlink_path.string())) - std::filesystem::create_directories(path + "/"); + std::filesystem::create_directories(_path + "/"); std::error_code ec; std::filesystem::create_directory_symlink(target, symlink_path, ec); if(ec){ error error( - constants::error::STD_ERROR, + constants::error::STD_ERR, std::format("Could not create symlink: {}", ec.message()) ); log::error(error); @@ -554,10 +565,10 @@ namespace gdpm::package{ ){ using namespace std::filesystem; - if(params.opts.empty()){ + if(params.args.empty()){ error error( - constants::error::PATH_NOT_DEFINED, - "No path set. Use '--path' option to set a path." + constants::error::INVALID_ARG_COUNT, + "Must supply at least 2 arguments (package name and path)" ); log::error(error); return error; @@ -565,18 +576,25 @@ namespace gdpm::package{ result_t r_cache = cache::get_package_info_by_title(package_titles); package::info_list p_found = {}; - package::info_list p_cache = r_cache.unwrap_unsafe(); + package::info_list p_cache = r_cache.unwrap_unsafe(); + + /* Check for installed packages to clone */ if(p_cache.empty()){ error error( constants::error::NO_PACKAGE_FOUND, - "Could not find any packages to clone." + "Could not find any packages to clone in cache." ); log::error(error); return error; } for(const auto& p_title : package_titles){ - auto found = std::find_if(p_cache.begin(), p_cache.end(), [&p_title](const package::info& p){ return p.title == p_title; }); + auto found = std::find_if( + p_cache.begin(), + p_cache.end(), + [&p_title](const package::info& p){ + return p.title == p_title; + }); if(found != p_cache.end()){ p_found.emplace_back(*found); } @@ -592,15 +610,17 @@ namespace gdpm::package{ } /* Get the storage paths for all packages to create clones */ - path_list paths = get(params.opts.at("--path")); + path_refs paths = path_refs{params.args.back()}; + // path_list paths = path_list({params.args.back()}); const path package_dir{config.packages_dir}; for(const auto& p : p_found){ for(const auto& path : paths){ - log::info("Cloning \"{}\" package to {}", p.title, path + "/" + p.title); + const string _path = string(path); + log::info("clone: \"{}\" -> {}", p.title, _path + "/" + p.title); std::filesystem::path from{config.packages_dir + "/" + p.title}; - std::filesystem::path to{path + "/" + p.title}; + std::filesystem::path to{_path + "/" + p.title}; if(!std::filesystem::exists(to.string())) - std::filesystem::create_directories(to); + std::filesystem::create_directories(to); /* This should only occur if using a --force flag */ /* TODO: Add an option to force overwriting (i.e. --overwrite) */ std::filesystem::copy(from, to, copy_options::update_existing | copy_options::recursive); @@ -609,19 +629,23 @@ namespace gdpm::package{ return error(); } - void print_list(const info_list& packages){ for(const auto& p : packages){ - log::println("{}/{}/{} {} id={}\n\tGodot {}, {}, {}, Last Modified: {}", + log::println( + GDPM_COLOR_BLUE"{}/" + GDPM_COLOR_RESET "{}/{}/{} " + GDPM_COLOR_GREEN "v{} " + GDPM_COLOR_CYAN "{} " + GDPM_COLOR_RESET "Godot {}, {}", p.support_level, + p.category, p.author, p.title, p.version, - p.asset_id, + p.modify_date, + // p.asset_id, p.godot_version, - p.cost, - p.category, - p.modify_date + p.cost ); } } @@ -629,16 +653,22 @@ namespace gdpm::package{ void print_list(const rapidjson::Document& json){ for(const auto& o : json["result"].GetArray()){ - log::println("{}/{}/{} {} id={}\n\tGodot {}, {}, {}, Last Modified: {}", + log::println( + GDPM_COLOR_BLUE"{}/" + GDPM_COLOR_CYAN "{}/" + GDPM_COLOR_RESET "{}/{} " + GDPM_COLOR_GREEN "v{} " + GDPM_COLOR_CYAN "{} " + GDPM_COLOR_RESET "Godot {}, {}", o["support_level"] .GetString(), + utils::to_lower(o["category"].GetString()), o["author"] .GetString(), o["title"] .GetString(), o["version_string"] .GetString(), - o["asset_id"] .GetString(), + o["modify_date"] .GetString(), + // o["asset_id"] .GetString(), o["godot_version"] .GetString(), - o["cost"] .GetString(), - o["category"] .GetString(), - o["modify_date"] .GetString() + o["cost"] .GetString() ); } } diff --git a/src/package_manager.cpp b/src/package_manager.cpp index 13aac4e..7305c00 100644 --- a/src/package_manager.cpp +++ b/src/package_manager.cpp @@ -36,18 +36,16 @@ */ namespace gdpm::package_manager{ - CURL *curl; - CURLcode res; - config::context config; - remote::repository_map remote_sources; - action_e action; + CURL *curl; + CURLcode res; + config::context config; + action_e action; // opts_t opts; bool skip_prompt = false; bool clean_tmp_dir = false; int priority = -1; - error initialize(int argc, char **argv){ // curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); @@ -83,166 +81,178 @@ namespace gdpm::package_manager{ error parse_arguments(int argc, char **argv){ + using namespace clipp; + /* Replace cxxopts with clipp */ action_e action = action_e::none; package::title_list package_titles; - string_list input; package::params params; - args_t args; - var_opts opts; + + auto doc_format = clipp::doc_formatting{} + .first_column(7) + .doc_column(45) + .last_column(99); /* Set global options */ - auto configOpt = clipp::option("--config-path").set(config.path)% "set config path"; - auto fileOpt = clipp::option("--file", "-f").set(input) % "read file as input"; - auto pathOpt = clipp::option("--path").set(params.paths) % "specify a path to use with command"; - auto typeOpt = clipp::option("--type").set(config.info.type) % "set package type (any|addon|project)"; - auto sortOpt = clipp::option("--sort").set(config.api_params.sort) % "sort packages in order (rating|cost|name|updated)"; - auto supportOpt = clipp::option("--support").set(config.api_params.support) % "set the support level for API (all|official|community|testing)"; - auto maxResultsOpt = clipp::option("--max-results").set(config.api_params.max_results) % "set the request max results"; - auto godotVersionOpt = clipp::option("--godot-version").set(config.api_params.godot_version) % "set the request Godot version"; - auto packageDirOpt = clipp::option("--package-dir").set(config.packages_dir) % "set the global package location"; - auto tmpDirOpt = clipp::option("--tmp-dir").set(config.tmp_dir) % "set the temporary download location"; - auto timeoutOpt = clipp::option("--timeout").set(config.timeout) % "set the request timeout"; - auto verboseOpt = clipp::option("--verbose", "-v").set(config.verbose) % "show verbose output"; + auto debugOpt = option("-d", "--debug").set(config.verbose, to_int(log::DEBUG)) % "show debug output"; + auto configOpt = option("--config-path").set(config.path) % "set config path"; + auto fileOpt = repeatable(option("--file", "-f").set(params.args) % "read file as input"); + auto pathOpt = option("--path").set(params.paths) % "specify a path to use with command"; + auto typeOpt = option("--type").set(config.info.type) % "set package type (any|addon|project)"; + auto sortOpt = option("--sort").set(config.api_params.sort) % "sort packages in order (rating|cost|name|updated)"; + auto supportOpt = option("--support").set(config.api_params.support) % "set the support level for API (all|official|community|testing)"; + auto maxResultsOpt = option("--max-results").set(config.api_params.max_results) % "set the request max results"; + auto godotVersionOpt = option("--godot-version").set(config.api_params.godot_version) % "set the request Godot version"; + auto packageDirOpt = option("--package-dir").set(config.packages_dir) % "set the global package location"; + auto tmpDirOpt = option("--tmp-dir").set(config.tmp_dir) % "set the temporary download location"; + auto timeoutOpt = option("--timeout").set(config.timeout) % "set the request timeout"; + auto verboseOpt = repeatable(option("-v", "--verbose", "-v").call([]{ config.verbose += 1; })) % "show verbose output"; /* Set the options */ - auto cleanOpt = clipp::option("--clean").set(config.clean_temporary) % "enable/disable cleaning temps"; - auto parallelOpt = clipp::option("--jobs").set(config.jobs) % "set number of parallel jobs"; - auto cacheOpt = clipp::option("--enable-cache").set(config.enable_cache) % "enable/disable local caching"; - auto syncOpt = clipp::option("--enable-sync").set(config.enable_sync) % "enable/disable remote syncing"; - auto skipOpt = clipp::option("--skip-prompt").set(config.skip_prompt) % "skip the y/n prompt"; - auto remoteOpt = clipp::option("--remote").set(params.remote_source) % "set remote source to use"; + auto cleanOpt = option("--clean").set(config.clean_temporary) % "enable/disable cleaning temps"; + auto parallelOpt = option("--jobs").set(config.jobs) % "set number of parallel jobs"; + auto cacheOpt = option("--enable-cache").set(config.enable_cache) % "enable/disable local caching"; + auto syncOpt = option("--enable-sync").set(config.enable_sync) % "enable/disable remote syncing"; + auto skipOpt = option("--skip-prompt").set(config.skip_prompt) % "skip the y/n prompt"; + auto remoteOpt = option("--remote").set(params.remote_source) % "set remote source to use"; - auto packageValues = clipp::values("packages", package_titles); - auto requiredPath = clipp::required("--path", input); - - auto installCmd = ( - clipp::command("install") - .set(action, action_e::install) - .doc("Install packages from asset library"), - clipp::values("packages", package_titles), - clipp::option("--godot-version") & clipp::value("version", config.info.godot_version), - cleanOpt, parallelOpt, syncOpt, skipOpt, remoteOpt + auto packageValues = values("packages", package_titles); + auto requiredPath = required("--path", params.args); + auto installCmd = "install" % ( + command("install").set(action, action_e::install), + packageValues % "packages to install from asset library", + godotVersionOpt, cleanOpt, parallelOpt, syncOpt, skipOpt, remoteOpt ); - auto addCmd = ( - clipp::command("add").set(action, action_e::add) - .doc("Add a package to a local project"), - packageValues + auto addCmd = "add" % ( + command("add").set(action, action_e::add), + packageValues % "package(s) to add to local project", + parallelOpt, skipOpt, remoteOpt ); - auto removeCmd = ( - clipp::command("remove") - .set(action, action_e::remove) - .doc("Remove a package from local project"), - packageValues + auto removeCmd = "remove" % ( + command("remove").set(action, action_e::remove), + packageValues % "package(s) to remove from local project" ); - auto updateCmd = ( - clipp::command("update") - .set(action, action_e::update) - .doc("Update package(s)"), - packageValues + auto updateCmd = "update" % ( + command("update").set(action, action_e::update), + packageValues % "update package(s)" ); - auto searchCmd = ( - clipp::command("search") - .set(action, action_e::search) - .doc("Search for package(s)"), - packageValues + auto searchCmd = "search" % ( + command("search").set(action, action_e::search), + packageValues % "package(s) to search for" ); - auto exportCmd = ( - clipp::command("export") - .set(action, action_e::p_export) - .doc("Export package list"), - clipp::values("path", input) + auto exportCmd = "export" % ( + command("export").set(action, action_e::p_export), + values("paths", params.args) % "export installed package list to file" ); - auto listCmd = ( - clipp::command("list") - .set(action, action_e::list) - .doc("Show installed packages") + auto listCmd = "show installed packages" % ( + command("list").set(action, action_e::list) ); - auto linkCmd = ( - clipp::command("link") - .set(action, action_e::link) - .doc("Create symlink packages to project"), - packageValues, - requiredPath + auto linkCmd = "link" % ( + command("link").set(action, action_e::link), + value("package", package_titles) % "package name to link", + value("path", params.args) % "path to project" ); - auto cloneCmd = ( - clipp::command("clone") - .set(action, action_e::clone) - .doc("Clone packages to project"), - packageValues, - requiredPath + auto cloneCmd = "clone" % ( + command("clone").set(action, action_e::clone), + value("package", package_titles) % "packages to clone", + value("path", params.args) % "path to project" ); - auto cleanCmd = ( - clipp::command("clean") - .set(action, action_e::clean) - .doc("Clean temporary files"), - packageValues + auto cleanCmd = "clean" % ( + command("clean").set(action, action_e::clean), + values("packages", package_titles) % "package temporary files to remove" ); - auto configCmd = ( - clipp::command("config") - .set(action, action_e::config) - .doc("Set/get config properties") + auto configCmd = "get/set config properties" % ( + command("config").set(action, action_e::config_get), + ( + ( greedy(command("get")).set(action, action_e::config_get), + option(repeatable(values("properties", params.args))) % "get config properties" + ) + | + ( command("set").set(action, action_e::config_set), + value("property", params.args[1]).call([]{}) % "config property", + value("value", params.args[2]).call([]{}) % "config value" + ) + ) ); - auto fetchCmd = ( - clipp::command("fetch") - .set(action, action_e::fetch) - .doc("Fetch asset metadata from remote source") + auto fetchCmd = "fetch" % ( + command("fetch").set(action, action_e::fetch), + option(values("remote", params.args)) % "remote to fetch asset data" ); + auto add_arg = [¶ms](string arg) { params.args.emplace_back(arg); }; auto remoteCmd = ( - clipp::command("remote") - .set(action, action_e::remote) - .doc("Manage remote sources") - .required("subcommand") + command("remote").set(action, action_e::remote_list).if_missing( + []{ + remote::print_repositories(config); + } + ), + ( + "add a remote source" % ( command("add").set(action, action_e::remote_add), + word("name").call(add_arg) % "remote name", + value("url").call(add_arg) % "remote URL" + ) + | + "remove a remote source" % ( command("remove").set(action, action_e::remote_remove), + words("names", params.args) % "remote name(s)" + ) + | + "list remote sources" % ( command("list").set(action, action_e::remote_list)) + ) ); - auto uiCmd = ( - clipp::command("ui") - .set(action, action_e::ui) - .doc("Show the UI") + auto uiCmd = "start with UI" % ( + command("ui").set(action, action_e::ui) ); - auto helpCmd = ( - clipp::command("help") - .set(action, action_e::help) + auto helpCmd = "show this message and exit" % ( + command("help").set(action, action_e::help) ); auto cli = ( + debugOpt, configOpt, (installCmd | addCmd | removeCmd | updateCmd | searchCmd | exportCmd | listCmd | linkCmd | cloneCmd | cleanCmd | configCmd | fetchCmd | remoteCmd | uiCmd | helpCmd) ); /* Make help output */ - string map_page_format(""); - auto man_page = clipp::make_man_page(cli); + string man_page_format(""); + auto man_page = make_man_page(cli, argv[0], doc_format) + .prepend_section("DESCRIPTION", "\tManage Godot Game Engine assets from the command-line.") + .append_section("LICENSE", "\tSee the 'LICENSE.md' file for more details."); std::for_each(man_page.begin(), man_page.end(), - [&map_page_format](const clipp::man_page::section& s){ - map_page_format += s.title() + "\n"; - map_page_format += s.content() + "\n"; + [&man_page_format](const man_page::section& s){ + man_page_format += s.title() + "\n"; + man_page_format += s.content() + "\n\n"; } ); + + // log::level = config.verbose; if(clipp::parse(argc, argv, cli)){ + log::level = config.verbose; switch(action){ - case action_e::install: package::install(config, package_titles, params); break; - case action_e::add: break; - case action_e::remove: package::remove(config, package_titles, params); break; - case action_e::update: package::update(config, package_titles, params); break; - case action_e::search: package::search(config, package_titles, params); break; - case action_e::p_export: package::export_to(input); break; - case action_e::list: package::list(config, params); break; - /* ...opts are the paths here */ - case action_e::link: package::link(config, package_titles, params); break; - case action_e::clone: package::clone(config, package_titles, params); break; - case action_e::clean: package::clean_temporary(config, package_titles); break; - case action_e::config: config::handle_config(config, package_titles, opts); break; - case action_e::fetch: package::synchronize_database(config, package_titles); break; - case action_e::sync: package::synchronize_database(config, package_titles); break; - case action_e::remote: remote::handle_remote(config, args, opts); break; - case action_e::ui: log::info("ui not implemented yet"); break; - case action_e::help: log::println("{}", map_page_format); break; - case action_e::none: /* ...here to run with no command */ break; + case action_e::install: package::install(config, package_titles, params); break; + case action_e::add: package::add(config, package_titles); + case action_e::remove: package::remove(config, package_titles, params); break; + case action_e::update: package::update(config, package_titles, params); break; + case action_e::search: package::search(config, package_titles, params); break; + case action_e::p_export: package::export_to(params.args); break; + case action_e::list: package::list(config, params); break; + /* ...opts are the paths here */ + case action_e::link: package::link(config, package_titles, params); break; + case action_e::clone: package::clone(config, package_titles, params); break; + case action_e::clean: package::clean_temporary(config, package_titles); break; + case action_e::config_get: config::print_properties(config, params.args); break; + case action_e::config_set: config::handle_config(config, package_titles, params.opts); break; + case action_e::fetch: package::synchronize_database(config, package_titles); break; + case action_e::sync: package::synchronize_database(config, package_titles); break; + case action_e::remote_list: remote::print_repositories(config); break; + case action_e::remote_add: remote::add_repository(config, params.args); break; + case action_e::remote_remove: remote::remove_respositories(config, params.args); break; + case action_e::ui: log::println("UI not implemented yet"); break; + case action_e::help: log::println("{}", man_page_format); break; + case action_e::none: /* ...here to run with no command */ break; } } else { - log::println("{}", map_page_format); + log::println("usage:\n{}", usage_lines(cli, argv[0]).str()); } return error(); } diff --git a/src/remote.cpp b/src/remote.cpp index f88909e..94dca06 100644 --- a/src/remote.cpp +++ b/src/remote.cpp @@ -6,88 +6,48 @@ #include namespace gdpm::remote{ - error handle_remote( - config::context& config, - const args_t& args, - const var_opts& opts + + error add_repository( + config::context &config, + const args_t &args ){ - /* Check if enough arguments are supplied */ - size_t argc = args.size(); - if (argc < 1){ - print_repositories(config); - return error(); + /* Check if enough args were provided. */ + log::debug("arg count: {}\nargs: {}", args.size(), utils::join(args)); + if (args.size() < 2){ + return error( + constants::error::INVALID_ARG_COUNT, + "Requires a remote name and url argument" + ); } - /* Check which subcommand is supplied */ - string sub_command = args.front(); - if(sub_command == "add"){ - if(args.size() < 3 || args.empty()){ - error error( - constants::error::INVALID_ARG_COUNT, - "Invalid number of args." - ); - log::error(error); - return error; - } - string name = args[1]; - string url = args[2]; - add_repositories(config, {{name, url}}); - } - else if (sub_command == "remove") { - if(args.size() < 2 || args.empty()){ - error error( - constants::error::INVALID_ARG_COUNT, - "Invalid number of args." - ); - log::error(error); - return error; - } - remove_respositories(config, {args.begin()+1, args.end()}); - } - // else if (sub_command == "set") set_repositories(config::context &context, const repository_map &repos) - else if (sub_command == "list") print_repositories(config); - else{ - error error( - constants::error::UNKNOWN, - "Unknown sub-command. Try 'gdpm help remote' for options." - ); - log::error(error); - return error; - } + /* Get the first two args */ + log::println("{}", args[0]); + config.remote_sources.insert({args[0], args[1]}); return error(); } - - void set_repositories( + + error remove_respositories( config::context& config, - const repository_map &repos + const args_t& args ){ - config.remote_sources = repos; - } + log::debug("arg count: {}\nargs: {}", args.size(), utils::join(args)); + if(args.size() < 1){ + return error( + constants::error::INVALID_ARG_COUNT, + "Requires at least one remote name argument" + ); + } - - void add_repositories( - config::context& config, - const repository_map &repos - ){ - std::for_each(repos.begin(), repos.end(), - [&config](const string_pair& p){ - config.remote_sources.insert(p); - } - ); - } - - - void remove_respositories( - config::context& config, - const repo_names& names - ){ - for(auto it = names.begin(); it != names.end();){ + for(auto it = args.begin(); it != args.end();){ if(config.remote_sources.contains(*it)){ + log::println("{}", *it); config.remote_sources.erase(*it); } it++; } + + return error(); } diff --git a/src/utils.cpp b/src/utils.cpp index 5189ca9..efcb1df 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,6 +1,7 @@ #include "utils.hpp" #include "config.hpp" +#include "constants.hpp" #include "log.hpp" @@ -13,9 +14,12 @@ #include #include #include +#include #include +#include #include +#include #include namespace gdpm::utils{ @@ -53,8 +57,38 @@ namespace gdpm::utils{ } #endif - void to_lower(std::string& s){ - std::transform(s.begin(), s.end(), s.begin(), tolower); + std::string to_lower(const std::string& s){ + std::string copy = s; + std::transform(copy.begin(), copy.end(), copy.begin(), tolower); + return copy; + } + + std::string trim(const std::string& s){ + return trim_right(trim_left(s)); + } + + std::string trim_left(const std::string& s){ + return trim_left(s, constants::WHITESPACE); + } + + std::string trim_left( + const std::string& s, + const std::string& ref + ){ + size_t start = s.find_first_not_of(ref); + return (start == std::string::npos) ? "" : s.substr(start); + } + + std::string trim_right(const std::string& s){ + return trim_right(s, constants::WHITESPACE); + } + + std::string trim_right( + const std::string& s, + const std::string& ref + ){ + size_t end = s.find_last_not_of(ref); + return (end == std::string::npos) ? "" : s.substr(0, end + 1); } std::vector parse_lines(const std::string &s){ @@ -97,7 +131,7 @@ namespace gdpm::utils{ const char *dest, int verbose ){ - const char *prog = "gpdm"; + constexpr const char *prog = "gpdm"; struct zip *za; struct zip_file *zf; struct zip_stat sb; @@ -198,9 +232,31 @@ namespace gdpm::utils{ const std::string& delimiter ){ std::string o; - std::for_each(target.begin(), target.end(), [&o, &delimiter](const std::string& s){ - o += s + delimiter; - }); + std::for_each( + target.begin(), + target.end(), + [&o, &delimiter](const std::string& s){ + o += s + delimiter; + } + ); + o = trim_right(o, delimiter); + return o; + } + + std::string join( + const std::unordered_map& target, + const std::string& prefix, + const std::string& delimiter + ){ + std::string o; + std::for_each( + target.begin(), + target.end(), + [&o, &prefix, &delimiter](const std::pair& p){ + o += prefix + p.first + ": " + p.second + delimiter; + } + ); + o = trim_right(o, delimiter); return o; } diff --git a/tests/plugin.cpp b/tests/plugin.cpp new file mode 100644 index 0000000..03f1509 --- /dev/null +++ b/tests/plugin.cpp @@ -0,0 +1,10 @@ +#include "plugin.hpp" + +#include + + +TEST_SUITE("Test example plugin"){ + TEST_CASE("Test initialization"){ + + } +} \ No newline at end of file