Change command-line parsing (again...)

- Added formatted table with `--style` option
- Added `warning` log level
- Fixed bugs and cleaned up API
- Removed some extra debugging output
This commit is contained in:
David Allen 2023-07-01 21:32:24 -06:00
parent 0238dfd510
commit b36d55ceee
15 changed files with 685 additions and 255 deletions

3
.gitmodules vendored
View file

@ -13,3 +13,6 @@
[submodule "modules/csv2"] [submodule "modules/csv2"]
path = modules/csv2 path = modules/csv2
url = https://github.com/p-ranav/csv2 url = https://github.com/p-ranav/csv2
[submodule "modules/argparse"]
path = modules/argparse
url = https://github.com/p-ranav/argparse

View file

@ -30,7 +30,7 @@ find_package(SQLiteCpp CONFIG REQUIRED)
set(CMAKE_CXX_COMPILER "clang++") set(CMAKE_CXX_COMPILER "clang++")
set(CMAKE_BUILD_RPATH "build/cmake") set(CMAKE_BUILD_RPATH "build/cmake")
set(CMAKE_CXX_FLAGS set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -std=c++20 -Ofast -fPIC -fPIE -fpermissive -Wall -Wno-switch -Wno-unused-variable -Wno-unused-function -Wno-sign-conversion -pedantic-errors" "${CMAKE_CXX_FLAGS} -std=c++20 -Ofast -fPIC -fPIE -fpermissive -Wall -Wno-switch -Wno-unused-variable -Wno-unused-function -Wno-sign-conversion -Wno-deprecated-declarations -pedantic-errors"
) )
set(INCLUDE_DIRS set(INCLUDE_DIRS
"include" "include"

View file

@ -17,6 +17,11 @@ namespace gdpm::package{
} }
namespace gdpm::config{ namespace gdpm::config{
enum class print_style{
list = 0,
table = 1,
};
struct context{ struct context{
string username; string username;
string password; string password;
@ -25,24 +30,28 @@ namespace gdpm::config{
string packages_dir; string packages_dir;
string tmp_dir; string tmp_dir;
string_map remote_sources; string_map remote_sources;
size_t jobs = 1; int jobs = 1;
size_t timeout = 3000; int timeout = 3000;
size_t max_results = 200;
bool enable_sync = true; bool enable_sync = true;
bool enable_cache = true; bool enable_cache = true;
bool skip_prompt = false; bool skip_prompt = false;
bool enable_file_logging; bool enable_file_logging;
bool clean_temporary; bool clean_temporary;
int verbose;
int verbose = log::INFO;
print_style style = print_style::list;
package::info info; package::info info;
rest_api::request_params api_params; rest_api::request_params rest_api_params;
}; };
string to_json(const context& config, bool pretty_print = false); string to_json(const context& config, bool pretty_print = false);
error load(std::filesystem::path path, context& config); error load(std::filesystem::path path, context& config);
error save(std::filesystem::path path, const context& config); error save(std::filesystem::path path, const context& config);
error handle_config(config::context& config, const args_t& args, const var_opts& opts); 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 set_property(config::context& config, const string& property, const any& value);
template <typename T = any>
T& get_property(const config::context& config, const string& property);
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}, int jobs = GDPM_CONFIG_THREADS, int 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); error validate(const rapidjson::Document& doc);
void print_json(const context& config); void print_json(const context& config);
void print_properties(const context& config, const string_list& properties); void print_properties(const context& config, const string_list& properties);

View file

@ -13,6 +13,8 @@ namespace gdpm::constants::error{
NONE = 0, NONE = 0,
UNKNOWN, UNKNOWN,
UNKNOWN_COMMAND, UNKNOWN_COMMAND,
UNKNOWN_ARGUMENT,
ARGPARSE_ERROR,
NOT_FOUND, NOT_FOUND,
NOT_DEFINED, NOT_DEFINED,
NOT_IMPLEMENTED, NOT_IMPLEMENTED,
@ -102,6 +104,11 @@ namespace gdpm{
#endif #endif
} }
static constexpr gdpm::error error_rc(const gdpm::error& e){
error(e);
return e;
}
static void error(const char *p, const gdpm::error& e){ static void error(const char *p, const gdpm::error& e){
println("{}{}{}", p, prefix.contents, e.get_message()); println("{}{}{}", p, prefix.contents, e.get_message());
} }

View file

@ -77,6 +77,7 @@ namespace gdpm::log
inline constexpr const char* get_info_prefix() { return "[INFO {}] "; } inline constexpr const char* get_info_prefix() { return "[INFO {}] "; }
inline constexpr const char* get_error_prefix() { return "[ERROR {}] "; } inline constexpr const char* get_error_prefix() { return "[ERROR {}] "; }
inline constexpr const char* get_debug_prefix() { return "[DEBUG {}] "; } inline constexpr const char* get_debug_prefix() { return "[DEBUG {}] "; }
inline constexpr const char* get_warning_prefix() { return "[WARN {}] "; }
static void vlog(fmt::string_view format, fmt::format_args args){ static void vlog(fmt::string_view format, fmt::format_args args){
fmt::vprint(format, args); fmt::vprint(format, args);
@ -142,6 +143,21 @@ namespace gdpm::log
#endif #endif
} }
template <typename S, typename...Args>
static constexpr void warn(const S& format, Args&&...args){
if(log::level < to_int(log::WARNING))
return;
#if GDPM_LOG_LEVEL > WARN
set_prefix_if(std::format(get_warning_prefix(), utils::timestamp()), true);
set_suffix_if("\n");
vlog(
fmt::format(GDPM_COLOR_LOG_WARNING "{}{}{}" GDPM_COLOR_LOG_RESET, prefix.contents, format, suffix),
fmt::make_format_args(args...)
);
#endif
}
template <typename S, typename...Args> template <typename S, typename...Args>
static constexpr void print(const S& format, Args&&...args){ static constexpr void print(const S& format, Args&&...args){
vlog( vlog(

View file

@ -116,6 +116,7 @@ namespace gdpm::package {
GDPM_DLL_EXPORT void print_list(const rapidjson::Document& json); GDPM_DLL_EXPORT void print_list(const rapidjson::Document& json);
GDPM_DLL_EXPORT void print_list(const info_list& packages); GDPM_DLL_EXPORT void print_list(const info_list& packages);
GDPM_DLL_EXPORT void print_table(const info_list& packages);
GDPM_DLL_EXPORT result_t<info_list> get_package_info(const opts_t& opts); GDPM_DLL_EXPORT result_t<info_list> get_package_info(const opts_t& opts);
GDPM_DLL_EXPORT result_t<title_list> get_package_titles(const info_list& packages); GDPM_DLL_EXPORT result_t<title_list> 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 void clean_temporary(const config::context& config, const title_list& package_titles);

View file

@ -1,11 +1,13 @@
#pragma once #pragma once
#include "clipp.h"
#include <tuple> #include <tuple>
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
#include <string> #include <string>
#include <variant> #include <variant>
#include <future> #include <future>
#include <any>
namespace gdpm{ namespace gdpm{
class error; class error;
@ -47,6 +49,7 @@ namespace gdpm{
using string_list = std::vector<string>; using string_list = std::vector<string>;
using string_map = std::unordered_map<string, string>; using string_map = std::unordered_map<string, string>;
using string_pair = std::pair<string, string>; using string_pair = std::pair<string, string>;
using any = std::any;
using var = std::variant<int, float, bool, string, string_list, string_map, size_t>; using var = std::variant<int, float, bool, string, string_list, string_map, size_t>;
template <typename T = var> template <typename T = var>
using _args_t = std::vector<T>; using _args_t = std::vector<T>;

1
modules/argparse Submodule

@ -0,0 +1 @@
Subproject commit 557948f1236db9e27089959de837cc23de6c6bbd

View file

@ -7,6 +7,7 @@
#include "types.hpp" #include "types.hpp"
// RapidJSON // RapidJSON
#include <any>
#include <rapidjson/ostreamwrapper.h> #include <rapidjson/ostreamwrapper.h>
#include <rapidjson/rapidjson.h> #include <rapidjson/rapidjson.h>
#include <rapidjson/writer.h> #include <rapidjson/writer.h>
@ -14,6 +15,7 @@
#include <rapidjson/prettywriter.h> #include <rapidjson/prettywriter.h>
#include <rapidjson/document.h> #include <rapidjson/document.h>
#include <rapidjson/error/en.h> #include <rapidjson/error/en.h>
#include <tabulate/table.hpp>
// fmt // fmt
@ -67,7 +69,7 @@ namespace gdpm::config{
file.open(path, std::ios::in); file.open(path, std::ios::in);
if(!file){ if(!file){
if(config.verbose) if(config.verbose)
log::info("No configuration file found. Creating a new one."); log::warn("No configuration file found. Creating a new one.");
config = make_context(); config = make_context();
save(config.path, config); save(config.path, config);
return error(); return error();
@ -230,6 +232,46 @@ namespace gdpm::config{
return error(); return error();
} }
error set_property(
config::context& config,
const string& property,
const any& value
){
log::println("config::set_property() called");
if(property == "username") config.username = std::any_cast<string>(value);
else if(property == "password") config.password = std::any_cast<string>(value);
else if(property == "path") config.path = std::any_cast<string>(value);
else if(property == "token") config.token = std::any_cast<string>(value);
else if(property == "packages_dir") config.packages_dir = std::any_cast<string>(value);
else if(property == "tmp_dir") config.tmp_dir = std::any_cast<string>(value);
else if(property == "remote_sources") config.remote_sources = std::any_cast<string_map>(value);
else if(property == "jobs") config.jobs = std::any_cast<int>(value);
else if(property == "timeout") config.timeout = std::any_cast<int>(value);
else if(property == "enable_sync") config.enable_sync = std::any_cast<bool>(value);
else if(property == "enable_cache") config.enable_cache = std::any_cast<bool>(value);
else if(property == "skip_prompt") config.skip_prompt = std::any_cast<bool>(value);
else if(property == "enable_file_logging") config.enable_file_logging = std::any_cast<bool>(value);
else if(property == "clean_temporary") config.clean_temporary = std::any_cast<bool>(value);
else{
return log::error_rc(error(
constants::error::INVALID_CONFIG,
"Could not find property"
));
}
}
template <typename T>
T& get_property(
const config::context& config,
const string& property
){
log::println("config::get_property() called");
if(property == "username") return config.username;
else if(property == "password") return config.password;
else if(property == "path") return config.path;
else if(property == "token") return config.token;
else if(property == "package_dir") return config.packages_dir;
}
context make_context( context make_context(
const string& username, const string& username,
@ -240,8 +282,8 @@ namespace gdpm::config{
const string& packages_dir, const string& packages_dir,
const string& tmp_dir, const string& tmp_dir,
const string_map& remote_sources, const string_map& remote_sources,
size_t threads, int jobs,
size_t timeout, int timeout,
bool enable_sync, bool enable_sync,
bool enable_file_logging, bool enable_file_logging,
int verbose int verbose
@ -254,7 +296,7 @@ namespace gdpm::config{
.packages_dir = (packages_dir.empty()) ? string(getenv("HOME")) + ".gdpm" : packages_dir, .packages_dir = (packages_dir.empty()) ? string(getenv("HOME")) + ".gdpm" : packages_dir,
.tmp_dir = tmp_dir, .tmp_dir = tmp_dir,
.remote_sources = remote_sources, .remote_sources = remote_sources,
.jobs = threads, .jobs = jobs,
.timeout = timeout, .timeout = timeout,
.enable_sync = enable_sync, .enable_sync = enable_sync,
.enable_file_logging = enable_file_logging, .enable_file_logging = enable_file_logging,
@ -293,6 +335,8 @@ namespace gdpm::config{
const context& config, const context& config,
const string& property const string& property
){ ){
using namespace tabulate;
if(property.empty()) return; if(property.empty()) return;
else if(property == "username") log::println("username: {}", config.username); else if(property == "username") log::println("username: {}", config.username);
else if(property == "password") log::println("password: {}", config.password); else if(property == "password") log::println("password: {}", config.password);
@ -311,34 +355,103 @@ namespace gdpm::config{
else if(property == "verbose") log::println("verbose: {}", config.verbose); else if(property == "verbose") log::println("verbose: {}", config.verbose);
} }
void add_row(
tabulate::Table& table,
const context& config,
const string property
){
if(property.empty()) return;
else if(property == "username") table.add_row({"Username", config.username});
else if(property == "password") table.add_row({"Password", config.password});
else if(property == "path") table.add_row({"Path", config.path});
else if(property == "token") table.add_row({"Token", config.token});
else if(property == "packages_dir") table.add_row({"Package Directory", config.packages_dir});
else if(property == "tmp_dir") table.add_row({"Temp Directory", config.tmp_dir});
else if(property == "remote_sources") table.add_row({"Remotes", utils::join(config.remote_sources, "\t", "\n")});
else if(property == "jobs") table.add_row({"Threads", std::to_string(config.jobs)});
else if(property == "timeout") table.add_row({"Timeout", std::to_string(config.timeout)});
else if(property == "sync") table.add_row({"Fetch Assets", std::to_string(config.enable_sync)});
else if(property == "cache") table.add_row({"Cache", std::to_string(config.enable_cache)});
else if(property == "prompt") table.add_row({"Skip Prompt", std::to_string(config.skip_prompt)});
else if(property == "logging") table.add_row({"File Logging", std::to_string(config.enable_file_logging)});
else if(property == "clean") table.add_row({"Clean Temporary", std::to_string(config.clean_temporary)});
else if(property == "verbose") table.add_row({"Verbosity", std::to_string(config.verbose)});
}
void print_properties( void print_properties(
const context& config, const context& config,
const string_list& properties const string_list& properties
){ ){
if(properties.empty()){ using namespace tabulate;
_print_property(config, "username");
_print_property(config, "password"); if(config.style == config::print_style::list){
_print_property(config, "path"); if(properties.empty()){
_print_property(config, "token"); _print_property(config, "username");
_print_property(config, "packages_dir"); _print_property(config, "password");
_print_property(config, "tmp_dir"); _print_property(config, "path");
_print_property(config, "remote_sources"); _print_property(config, "token");
_print_property(config, "jobs"); _print_property(config, "packages_dir");
_print_property(config, "timeout"); _print_property(config, "tmp_dir");
_print_property(config, "sync"); _print_property(config, "remote_sources");
_print_property(config, "cache"); _print_property(config, "jobs");
_print_property(config, "prompt"); _print_property(config, "timeout");
_print_property(config, "logging"); _print_property(config, "sync");
_print_property(config, "clean"); _print_property(config, "cache");
_print_property(config, "verbose"); _print_property(config, "prompt");
} _print_property(config, "logging");
std::for_each( _print_property(config, "clean");
properties.begin(), _print_property(config, "verbose");
properties.end(),
[&config](const string& property){
_print_property(config, property);
} }
); else {
std::for_each(
properties.begin(),
properties.end(),
[&config](const string& property){
_print_property(config, property);
}
);
}
}
else if(config.style == config::print_style::table){
Table table;
if(properties.empty()){
table.add_row({"Property", "Value"});
table.add_row({"Username", config.username});
table.add_row({"Password", config.password});
table.add_row({"Path", config.path});
table.add_row({"Token", config.token});
table.add_row({"Package Directory", config.token});
table.add_row({"Temp Directory", config.tmp_dir});
table.add_row({"Remotes", utils::join(config.remote_sources)});
table.add_row({"Threads", std::to_string(config.jobs)});
table.add_row({"Timeout", std::to_string(config.timeout)});
table.add_row({"Fetch Data", std::to_string(config.enable_sync)});
table.add_row({"Use Cache", std::to_string(config.enable_cache)});
table.add_row({"Logging", std::to_string(config.enable_file_logging)});
table.add_row({"Clean", std::to_string(config.clean_temporary)});
table.add_row({"Verbosity", std::to_string(config.verbose)});
}
else{
std::for_each(
properties.begin(),
properties.end(),
[&table, &config](const string& property){
add_row(table, config, property);
}
);
}
table[0].format()
.padding_top(1)
.padding_bottom(1)
.font_background_color(Color::red)
.font_style({FontStyle::bold});
table.column(1).format()
.font_color(Color::yellow);
table[0][1].format()
.font_background_color(Color::blue);
table.print(std::cout);
}
} }
} }

View file

@ -15,6 +15,7 @@
#include <rapidjson/error/en.h> #include <rapidjson/error/en.h>
#include <rapidjson/ostreamwrapper.h> #include <rapidjson/ostreamwrapper.h>
#include <rapidjson/prettywriter.h> #include <rapidjson/prettywriter.h>
#include <tabulate/table.hpp>
namespace gdpm::package{ namespace gdpm::package{
@ -39,8 +40,7 @@ namespace gdpm::package{
*/ */
/* Append files from --file option */ /* Append files from --file option */
if(!params.input_files.empty()){ // if(!params.input_files.empty()){
log::print("input files");
for(const auto& filepath : params.input_files){ for(const auto& filepath : params.input_files){
string contents = utils::readfile(filepath); string contents = utils::readfile(filepath);
log::print("contents: {}", contents); log::print("contents: {}", contents);
@ -51,7 +51,7 @@ namespace gdpm::package{
std::end(input_titles) std::end(input_titles)
); );
} }
} // }
result_t result = cache::get_package_info_by_title(package_titles); result_t result = cache::get_package_info_by_title(package_titles);
package::info_list p_found = {}; package::info_list p_found = {};
package::info_list p_cache = result.unwrap_unsafe(); package::info_list p_cache = result.unwrap_unsafe();
@ -445,16 +445,14 @@ namespace gdpm::package{
rest_api_params.filter = http.url_escape(p_title); rest_api_params.filter = http.url_escape(p_title);
std::string request_url{constants::HostUrl}; string request_url{constants::HostUrl};
request_url += rest_api::endpoints::GET_Asset; request_url += rest_api::endpoints::GET_Asset;
Document doc = rest_api::get_assets_list(request_url, rest_api_params); Document doc = rest_api::get_assets_list(request_url, rest_api_params);
if(doc.IsNull()){ if(doc.IsNull()){
error error( return log::error_rc(error(
constants::error::HOST_UNREACHABLE, constants::error::HOST_UNREACHABLE,
"Could not fetch metadata." "Could not fetch metadata."
); ));
log::error(error);
return error;
} }
// log::info("{} package(s) found...", doc["total_items"].GetInt()); // log::info("{} package(s) found...", doc["total_items"].GetInt());
@ -476,18 +474,21 @@ namespace gdpm::package{
result_t r_installed = cache::get_installed_packages(); result_t r_installed = cache::get_installed_packages();
info_list p_installed = r_installed.unwrap_unsafe(); info_list p_installed = r_installed.unwrap_unsafe();
if(!p_installed.empty()){ if(!p_installed.empty()){
print_list(p_installed); if(config.style == config::print_style::list)
print_list(p_installed);
else if(config.style == config::print_style::table){
print_table(p_installed);
}
} }
} }
else if(show == "remote"){ else if(show == "remote"){
remote::print_repositories(config); remote::print_repositories(config);
} }
else{ else{
error error( log::error(error(
constants::error::UNKNOWN_COMMAND, constants::error::UNKNOWN_COMMAND,
"Unrecognized subcommand. Try either 'packages' or 'remote' instead." "Unrecognized subcommand. Try either 'packages' or 'remote' instead."
); ));
log::error(error);
} }
return error(); return error();
} }
@ -520,7 +521,7 @@ namespace gdpm::package{
} }
} }
std::ofstream of(path); std::ofstream of(path);
log::println("writing contents to file"); log::println("export: {}", path);
of << output; of << output;
of.close(); of.close();
} }
@ -532,17 +533,15 @@ namespace gdpm::package{
error link( error link(
const config::context& config, const config::context& config,
const title_list& package_titles, const title_list& package_titles,
const package::params& params /* path is last arg */ const package::params& params
){ ){
using namespace std::filesystem; using namespace std::filesystem;
if(params.args.empty()){ if(params.paths.empty()){
error error( return log::error_rc(error(
constants::error::INVALID_ARG_COUNT, constants::error::PATH_NOT_DEFINED,
"Must supply at least 2 arguments (package name and path)" "Path is required"
); ));
log::error(error);
return error;
} }
/* Check for packages in cache to link */ /* Check for packages in cache to link */
@ -550,12 +549,10 @@ namespace gdpm::package{
info_list p_found = {}; info_list p_found = {};
info_list p_cache = r_cache.unwrap_unsafe(); info_list p_cache = r_cache.unwrap_unsafe();
if(p_cache.empty()){ if(p_cache.empty()){
error error( return log::error_rc(error(
constants::error::NOT_FOUND, constants::error::NOT_FOUND,
"Could not find any packages to link in cache." "Could not find any packages to link in cache."
); ));
log::error(error);
return error;
} }
for(const auto& p_title : package_titles){ for(const auto& p_title : package_titles){
@ -575,17 +572,15 @@ namespace gdpm::package{
} }
/* Get the storage paths for all packages to create symlinks */ /* 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}; const path package_dir{config.packages_dir};
for(const auto& p : p_found){ for(const auto& p : p_found){
for(const auto& path : paths){ for(const auto& path : params.paths){
const string _path = path; log::info_n("link: \"{}\" -> '{}'...", p.title, path + "/" + p.title);
log::info_n("link: \"{}\" -> '{}'...", p.title, _path + "/" + p.title);
// std::filesystem::path target{config.packages_dir + "/" + 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 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())) if(!std::filesystem::exists(symlink_path.string()))
std::filesystem::create_directories(_path + "/"); std::filesystem::create_directories(path + "/");
std::error_code ec; std::error_code ec;
std::filesystem::create_directory_symlink(target, symlink_path, ec); std::filesystem::create_directory_symlink(target, symlink_path, ec);
if(ec){ if(ec){
@ -609,13 +604,11 @@ namespace gdpm::package{
){ ){
using namespace std::filesystem; using namespace std::filesystem;
if(params.args.empty()){ if(params.paths.empty()){
error error( return log::error_rc(error(
constants::error::INVALID_ARG_COUNT, constants::error::PATH_NOT_DEFINED,
"Must supply at least 2 arguments (package name and path)" "Path is required"
); ));
log::error(error);
return error;
} }
result_t r_cache = cache::get_package_info_by_title(package_titles); result_t r_cache = cache::get_package_info_by_title(package_titles);
@ -624,12 +617,10 @@ namespace gdpm::package{
/* Check for installed packages to clone */ /* Check for installed packages to clone */
if(p_cache.empty()){ if(p_cache.empty()){
error error( return log::error_rc(error(
constants::error::NO_PACKAGE_FOUND, constants::error::NO_PACKAGE_FOUND,
"Could not find any packages to clone in cache." "Could not find any packages to clone in cache."
); ));
log::error(error);
return error;
} }
for(const auto& p_title : package_titles){ for(const auto& p_title : package_titles){
@ -694,7 +685,6 @@ namespace gdpm::package{
} }
} }
void print_list(const rapidjson::Document& json){ void print_list(const rapidjson::Document& json){
for(const auto& o : json["result"].GetArray()){ for(const auto& o : json["result"].GetArray()){
log::println( log::println(
@ -717,6 +707,34 @@ namespace gdpm::package{
} }
} }
void print_table(const info_list& packages){
using namespace tabulate;
Table table;
table.add_row({
"Asset Name",
"Author",
"Category",
"Version",
"Godot Version",
"License/Cost",
"Last Modified",
"Support"
});
for(const auto& p : packages){
table.add_row({
p.title,
p.author,
p.category,
p.version,
p.godot_version,
p.cost,
p.modify_date,
p.support_level
});
}
table.print(std::cout);
}
result_t<info_list> get_package_info(const title_list& package_titles){ result_t<info_list> get_package_info(const title_list& package_titles){
return cache::get_package_info_by_title(package_titles); return cache::get_package_info_by_title(package_titles);

View file

@ -22,9 +22,11 @@
#include <rapidjson/document.h> #include <rapidjson/document.h>
#include <cxxopts.hpp> #include <cxxopts.hpp>
#include "clipp.h" #include "clipp.h"
#include "argparse/argparse.hpp"
#include <rapidjson/ostreamwrapper.h> #include <rapidjson/ostreamwrapper.h>
#include <rapidjson/prettywriter.h> #include <rapidjson/prettywriter.h>
#include <stdexcept>
#include <system_error> #include <system_error>
#include <future> #include <future>
@ -79,185 +81,426 @@ namespace gdpm::package_manager{
return error; return error;
} }
template <typename T, typename String = string>
auto set_if_used(
const argparse::ArgumentParser& cmd,
T& value,
const String& arg
){
using namespace argparse;
if(cmd.is_used(arg)){
value = cmd.get<T>(arg);
}
};
string_list get_packages_from_parser(
const argparse::ArgumentParser& cmd,
const std::string& arg = "packages"
){
if(cmd.is_used(arg))
return cmd.get<string_list>(arg);
return string_list();
}
error parse_arguments(int argc, char **argv){ error parse_arguments(int argc, char **argv){
using namespace clipp; using namespace clipp;
using namespace argparse;
/* Replace cxxopts with clipp */ /* Replace cxxopts with clipp */
action_e action = action_e::none; action_e action = action_e::none;
package::title_list package_titles; package::title_list package_titles;
package::params params; package::params params;
auto doc_format = clipp::doc_formatting{} ArgumentParser program(argv[0], "0.0.1", argparse::default_arguments::help);
.first_column(7) ArgumentParser install_command("install");
.doc_column(45) ArgumentParser add_command("add");
.last_column(99); ArgumentParser remove_command("remove");
ArgumentParser update_command("update");
ArgumentParser search_command("search");
ArgumentParser export_command("export");
ArgumentParser list_command("list");
ArgumentParser link_command("link");
ArgumentParser clone_command("clone");
ArgumentParser clean_command("clean");
ArgumentParser config_command("config");
ArgumentParser fetch_command("fetch");
ArgumentParser version_command("version");
ArgumentParser remote_command("remote");
ArgumentParser ui_command("ui");
ArgumentParser help_command("help");
/* Set global options */ ArgumentParser config_get("get");
auto debugOpt = option("-d", "--debug").set(config.verbose, to_int(log::DEBUG)) % "show debug output"; ArgumentParser config_set("set");
auto configOpt = option("--config-path").set(config.path) % "set config path";
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.rest_api_params.sort) % "sort packages in order (rating|cost|name|updated)";
auto supportOpt = option("--support").set(config.rest_api_params.support) % "set the support level for API (all|official|community|testing)";
auto maxResultsOpt = option("--max-results").set(config.rest_api_params.max_results) % "set the request max results";
auto godotVersionOpt = option("--godot-version").set(config.rest_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 = joinable(repeatable(option("-v", "--verbose").call([]{ config.verbose += 1; }))) % "show verbose output";
/* Set the options */ ArgumentParser remote_add("add");
// auto fileOpt = repeatable(option("--file", "-f").set(params.input_files) % "read file as input"); ArgumentParser remote_remove("remove");
auto fileOpt = repeatable(option("--file", "-f") & values("input", params.input_files)) % "read file as input"; ArgumentParser remote_list("list");
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("-y", "--skip-prompt").set(config.skip_prompt, true) % "skip the y/n prompt";
auto remoteOpt = option("--remote").set(params.remote_source) % "set remote source to use";
auto packageValues = values("packages", package_titles); program.add_description("Manage Godot engine assets from CLI");
auto requiredPath = required("--path", params.args); program.add_argument("-v", "--verbose")
.action([&](const auto&){ config.verbose += 1; })
.default_value(false)
.implicit_value(true)
.help("set verbosity level")
.nargs(0);
auto installCmd = "install" % ( install_command.add_description("install package(s)");
command("install").set(action, action_e::install), install_command.add_argument("packages")
packageValues % "package(s) to install from asset library", .required()
godotVersionOpt, cleanOpt, parallelOpt, syncOpt, skipOpt, remoteOpt, fileOpt .nargs(nargs_pattern::at_least_one)
); .help("packages to install");
auto addCmd = "add" % ( install_command.add_argument("--godot-version")
command("add").set(action, action_e::add), .help("set Godot version for request");
packageValues % "package(s) to add to project", install_command.add_argument("--clean")
parallelOpt, skipOpt, remoteOpt, fileOpt .help("clean temporary files")
); .implicit_value(true)
auto removeCmd = "remove" % ( .default_value(false)
command("remove").set(action, action_e::remove), .nargs(0);
packageValues % "packages(s) to remove from project", install_command.add_argument("--disable-sync")
fileOpt, skipOpt, cleanOpt .help("enable syncing with remote before installing")
); .implicit_value(true)
auto updateCmd = "update package(s)" % ( .default_value(false)
command("update").set(action, action_e::update), .nargs(0);
packageValues % "" install_command.add_argument("--disable-cache")
); .help("disable caching asset data")
auto searchCmd = "search for package(s)" % ( .implicit_value(true)
command("search").set(action, action_e::search), .default_value(false)
packageValues % "", .nargs(0);
godotVersionOpt, fileOpt, remoteOpt, configOpt install_command.add_argument("--remote")
); .help("set the remote to use")
auto exportCmd = "export installed package list to file" % ( .nargs(1);
command("export").set(action, action_e::p_export), install_command.add_argument("-j", "--jobs")
values("paths", params.args) % "" .help("set the number of parallel downloads")
); .default_value(1)
auto listCmd = "show installed packages" % ( .nargs(1)
command("list").set(action, action_e::list) .scan<'i', int>();
); install_command.add_argument("-y", "--skip-prompt")
auto linkCmd = "create link from package to project" % ( .help("skip the yes/no prompt")
command("link").set(action, action_e::link), .implicit_value(true)
value("package", package_titles) % "", .default_value(false)
value("path", params.args) % "" .nargs(0);
); install_command.add_argument("-f", "--file")
auto cloneCmd = "clone package to project" % ( .help("set the file(s) to read as input")
command("clone").set(action, action_e::clone), .append()
value("package", package_titles) % "", .nargs(1);
value("path", params.args) % "" install_command.add_argument("-t", "--timeout")
); .help("set the request timeout")
auto cleanCmd = "clean temporary download files" % ( .default_value(30)
command("clean").set(action, action_e::clean), .nargs(0);
values("packages", package_titles) % ""
);
auto configCmd = "manage 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([]{}),
value("value", params.args[2]).call([]{})
) % "set config properties"
)
);
auto fetchCmd = "fetch asset data from remote" % (
command("fetch").set(action, action_e::fetch),
option(values("remote", params.args)) % ""
);
auto versionCmd = "show the version and exit" %(
command("version").set(action, action_e::version)
);
auto add_arg = [&params](string arg) { params.args.emplace_back(arg); };
auto remoteCmd = "manage remote sources" % (
command("remote").set(action, action_e::remote_list).if_missing(
[]{ remote::print_repositories(config); }
),
(
"add" % ( command("add").set(action, action_e::remote_add),
word("name").call(add_arg) % "",
value("url").call(add_arg) % ""
)
|
"remove a remote source" % ( command("remove").set(action, action_e::remote_remove),
words("names", params.args) % ""
)
|
"list remote sources" % ( command("list").set(action, action_e::remote_list))
)
);
auto uiCmd = "start with UI" % (
command("ui").set(action, action_e::ui)
);
auto helpCmd = "show this message and exit" % (
command("help").set(action, action_e::help)
);
auto cli = ( add_command.add_description("add package to project");
debugOpt, verboseOpt, configOpt, add_command.add_argument("packages").nargs(nargs_pattern::at_least_one);
(installCmd | addCmd | removeCmd | updateCmd | searchCmd | exportCmd | add_command.add_argument("--remote");
listCmd | linkCmd | cloneCmd | cleanCmd | configCmd | fetchCmd | add_command.add_argument("-j", "--jobs")
remoteCmd | uiCmd | helpCmd | versionCmd) .help("")
); .nargs(1)
.default_value(1)
.nargs(1)
.scan<'i', int>();
add_command.add_argument("-y", "--skip-prompt");
add_command.add_argument("-f", "--file")
.help("set the file(s) to read as input")
.append()
.nargs(nargs_pattern::at_least_one);
/* Make help output */ remove_command.add_description("remove package(s)");
string man_page_format(""); remove_command.add_argument("packages").nargs(nargs_pattern::at_least_one);
auto man_page = make_man_page(cli, argv[0], doc_format) remove_command.add_argument("--clean");
.prepend_section("DESCRIPTION", "\tManage Godot Game Engine assets from the command-line.") remove_command.add_argument("-y", "--skip-prompt");
.append_section("LICENSE", "\tSee the 'LICENSE.md' file for more details."); remove_command.add_argument("-f", "--file")
std::for_each(man_page.begin(), man_page.end(), .help("set the file(s) to read as input")
[&man_page_format](const man_page::section& s){ .append()
man_page_format += s.title() + "\n"; .nargs(nargs_pattern::at_least_one);
man_page_format += s.content() + "\n\n";
update_command.add_description("update package(s)");
update_command.add_argument("packages").nargs(nargs_pattern::at_least_one);
update_command.add_argument("--clean");
update_command.add_argument("--remote");
update_command.add_argument("-f", "--file")
.help("set the file(s) to read as input")
.append()
.nargs(nargs_pattern::at_least_one);
search_command.add_description("search for package(s)");
search_command.add_argument("packages").nargs(nargs_pattern::at_least_one);
search_command.add_argument("--godot-version");
search_command.add_argument("--remote");
search_command.add_argument("-f", "--file")
.help("set the file(s) to read as input")
.append()
.nargs(nargs_pattern::at_least_one);
ui_command.add_description("show user interface (WIP)");
version_command.add_description("show version and exit");
help_command.add_description("show help message and exit");
export_command.add_description("export install package(s) list");
export_command.add_argument("paths")
.help("export list of installed packages")
.required()
.nargs(nargs_pattern::at_least_one);
list_command.add_description("show install package(s) and remotes");
list_command.add_argument("show")
.help("show installed packages or remote")
.nargs(nargs_pattern::any)
.default_value("packages");
list_command.add_argument("--style")
.help("set how to print output")
.nargs(1)
.default_value("list");
link_command.add_description("link package(s) to path");
link_command.add_argument("packages")
.help("package(s) to link")
.required()
.nargs(1);
link_command.add_argument("path")
.help("path to link")
.required()
.nargs(1);
clone_command.add_description("clone package(s) to path");
clone_command.add_argument("packages")
.help("package(s) to clone")
.required()
.nargs(1);;
clone_command.add_argument("path")
.help("path to clone")
.required()
.nargs(1);
clean_command.add_description("clean package(s) temporary files");
clean_command.add_argument("packages")
.help("package(s) to clean")
.required()
.nargs(nargs_pattern::at_least_one);
fetch_command.add_description("fetch and sync asset data");
fetch_command.add_argument("remote")
.help("remote to fetch")
.required()
.nargs(1);
config_get.add_argument("properties")
.help("get config properties")
.nargs(nargs_pattern::any);
config_get.add_description("get config properties");
config_set.add_argument("property")
.help("property name")
.required()
.nargs(1);
config_set.add_argument("value")
.help("property value")
.required()
.nargs(1);
config_set.add_description("set config property");
config_command.add_description("manage config properties");
config_command.add_subparser(config_get);
config_command.add_subparser(config_set);
remote_add.add_argument("name")
.help("remote name")
.nargs(1);
remote_add.add_argument("url")
.help("remote url")
.nargs(1);
remote_remove.add_argument("names")
.help("remote name")
.nargs(nargs_pattern::at_least_one);
remote_list.add_argument("--style")
.help("set print style")
.nargs(1);
remote_command.add_description("manage remote(s)");
remote_command.add_subparser(remote_add);
remote_command.add_subparser(remote_remove);
remote_command.add_subparser(remote_list);
// version_command.add_argument(Targs f_args...)
program.add_subparser(install_command);
program.add_subparser(add_command);
program.add_subparser(remove_command);
program.add_subparser(update_command);
program.add_subparser(search_command);
program.add_subparser(export_command);
program.add_subparser(list_command);
program.add_subparser(link_command);
program.add_subparser(clone_command);
program.add_subparser(clean_command);
program.add_subparser(config_command);
program.add_subparser(fetch_command);
program.add_subparser(remote_command);
program.add_subparser(version_command);
program.add_subparser(ui_command);
program.add_subparser(help_command);
try{
program.parse_args(argc, argv);
// program.parse_known_args(argc, argv);
} catch(const std::runtime_error& e){
return log::error_rc(error(
constants::error::ARGPARSE_ERROR,
e.what())
);
}
if(program.is_subcommand_used(install_command)){
action = action_e::install;
if(install_command.is_used("packages"))
package_titles = install_command.get<string_list>("packages");
set_if_used(install_command, config.rest_api_params.godot_version, "godot-version");
set_if_used(install_command, config.clean_temporary, "clean");
set_if_used(install_command, config.enable_sync, "disable-sync");
set_if_used(install_command, config.enable_cache, "disable-cache");
set_if_used(install_command, params.remote_source, "remote");
set_if_used(install_command, config.jobs, "jobs");
set_if_used(install_command, config.skip_prompt, "skip-prompt");
set_if_used(install_command, params.input_files, "file");
set_if_used(install_command, config.timeout, "timeout");
}
else if(program.is_subcommand_used(add_command)){
action = action_e::add;
package_titles = get_packages_from_parser(add_command);
set_if_used(add_command, params.remote_source, "remote");
set_if_used(add_command, config.jobs, "jobs");
set_if_used(add_command, config.skip_prompt, "skip-prompt");
set_if_used(add_command, params.input_files, "files");
}
else if(program.is_subcommand_used(remove_command)){
action = action_e::remove;
package_titles = get_packages_from_parser(remove_command);
set_if_used(remove_command, config.clean_temporary, "clean");
set_if_used(remove_command, config.skip_prompt, "skip-prompt");
set_if_used(remove_command, params.input_files, "file");
}
else if(program.is_subcommand_used(update_command)){
action = action_e::update;
package_titles = get_packages_from_parser(program);
set_if_used(update_command, config.clean_temporary, "clean");
set_if_used(update_command, params.remote_source, "remote");
set_if_used(update_command, params.input_files, "file");
}
else if(program.is_subcommand_used(search_command)){
action = action_e::search;
package_titles = get_packages_from_parser(search_command);
set_if_used(search_command, config.rest_api_params.godot_version, "godot-version");
set_if_used(search_command, params.remote_source, "remote");
set_if_used(search_command, params.input_files, "file");
}
else if(program.is_subcommand_used(export_command)){
action = action_e::p_export;
params.paths = export_command.get<string_list>("paths");
}
else if(program.is_subcommand_used(list_command)){
action = action_e::list;
// auto list = get_parser(program, "list");
if(list_command.is_used("show"))
params.args = list_command.get<string_list>("show");
if(list_command.is_used("style")){
string style = list_command.get<string>("style");
if(!style.compare("list"))
config.style = config::print_style::list;
else if(!style.compare("table"))
config.style = config::print_style::table;
} }
); }
else if(program.is_subcommand_used(link_command)){
// log::level = config.verbose; action = action_e::link;
if(clipp::parse(argc, argv, cli)){ package_titles = get_packages_from_parser(link_command);
log::level = config.verbose; set_if_used(link_command, params.paths, "path");
switch(action){ }
case action_e::install: package::install(config, package_titles, params); break; else if(program.is_subcommand_used(clone_command)){
case action_e::add: package::add(config, package_titles); action = action_e::clone;
case action_e::remove: package::remove(config, package_titles, params); break; package_titles = get_packages_from_parser(clone_command);
case action_e::update: package::update(config, package_titles, params); break; set_if_used(clone_command, params.paths, "path");
case action_e::search: package::search(config, package_titles, params); break; }
case action_e::p_export: package::export_to(params.args); break; else if(program.is_subcommand_used(clean_command)){
case action_e::list: package::list(config, params); break; action = action_e::clean;
/* ...opts are the paths here */ package_titles = get_packages_from_parser(clean_command);
case action_e::link: package::link(config, package_titles, params); break; }
case action_e::clone: package::clone(config, package_titles, params); break; else if(program.is_subcommand_used(config_command)){
case action_e::clean: package::clean_temporary(config, package_titles); break; if(config_command.is_subcommand_used(config_get)){
case action_e::config_get: config::print_properties(config, params.args); break; action = action_e::config_get;
case action_e::config_set: config::handle_config(config, package_titles, params.opts); break; if(config_get.is_used("properties"))
case action_e::fetch: package::synchronize_database(config, package_titles); break; params.args = config_get.get<string_list>("properties");
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::version: break;
case action_e::none: /* ...here to run with no command */ break;
} }
} else { else if(config_command.is_subcommand_used(config_set)){
log::println("usage:\n{}", usage_lines(cli, argv[0]).str()); action = action_e::config_set;
if(config_set.is_used("property"))
params.args.emplace_back(config_set.get<string>("property"));
if(config_set.is_used("value"))
params.args.emplace_back(config_set.get<string>("value"));
}
// else{
// action = action_e::config_get;
// }
}
else if(program.is_subcommand_used(fetch_command)){
action = action_e::fetch;
params.remote_source = fetch_command.get("remote");
}
else if(program.is_subcommand_used(version_command)){
action = action_e::version;
}
else if(program.is_subcommand_used(remote_command)){
if(remote_command.is_subcommand_used(remote_add)){
action = action_e::remote_add;
if(remote_add.is_used("name"))
params.args.emplace_back(remote_add.get<string>("name"));
if(remote_add.is_used("url"))
params.args.emplace_back(remote_add.get<string>("url"));
for(const auto& arg: params.args){
log::println("{}: {}", params.args[0], params.args[1]);
}
}
if(remote_command.is_subcommand_used(remote_remove)){
action = action_e::remote_remove;
if(remote_remove.is_used("names"))
params.args = remote_remove.get<string_list>("names");
}
if(remote_command.is_subcommand_used(remote_list)){
action = action_e::remote_list;
string style = remote_list.get<string>("style");
if(!style.compare("list"))
config.style = config::print_style::list;
else if(!style.compare("table"))
config.style = config::print_style::table;
}
}
else if(program.is_subcommand_used("ui")){
action = action_e::ui;
}
else if(program.is_subcommand_used("help")){
action = action_e::help;
}
switch(action){
case action_e::install: package::install(config, package_titles, params); break;
case action_e::add: package::add(config, package_titles, params);
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.paths); 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::set_property(config, params.args[0], params.args[1]); 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"); break;
case action_e::help: program.print_help(); break;
case action_e::version: break;
case action_e::none: program.usage(); break;/* ...here to run with no command */ break;
} }
return error(); return error();
} }

View file

@ -1,9 +1,11 @@
#include "remote.hpp" #include "remote.hpp"
#include "config.hpp"
#include "error.hpp" #include "error.hpp"
#include "log.hpp" #include "log.hpp"
#include "types.hpp" #include "types.hpp"
#include <readline/readline.h> #include <readline/readline.h>
#include <tabulate/table.hpp>
namespace gdpm::remote{ namespace gdpm::remote{
@ -12,7 +14,7 @@ namespace gdpm::remote{
const args_t &args const args_t &args
){ ){
/* Check if enough args were provided. */ /* Check if enough args were provided. */
log::debug("arg count: {}\nargs: {}", args.size(), utils::join(args)); log::println("arg count: {}\nargs: {}", args.size(), utils::join(args));
if (args.size() < 2){ if (args.size() < 2){
return error( return error(
constants::error::INVALID_ARG_COUNT, constants::error::INVALID_ARG_COUNT,
@ -31,7 +33,7 @@ namespace gdpm::remote{
config::context& config, config::context& config,
const args_t& args const args_t& args
){ ){
log::debug("arg count: {}\nargs: {}", args.size(), utils::join(args)); log::println("arg count: {}\nargs: {}", args.size(), utils::join(args));
if(args.size() < 1){ if(args.size() < 1){
return error( return error(
constants::error::INVALID_ARG_COUNT, constants::error::INVALID_ARG_COUNT,
@ -61,8 +63,19 @@ namespace gdpm::remote{
void print_repositories(const config::context& config){ void print_repositories(const config::context& config){
const auto &rs = config.remote_sources; const auto &rs = config.remote_sources;
std::for_each(rs.begin(), rs.end(), [](const string_pair& p){ if(config.style == config::print_style::list){
log::println("{}: {}", p.first, p.second); std::for_each(rs.begin(), rs.end(), [](const string_pair& p){
}); log::println("{}: {}", p.first, p.second);
});
}
else if(config.style == config::print_style::table){
using namespace tabulate;
Table table;
table.add_row({"Name", "URL"});
std::for_each(rs.begin(), rs.end(), [&table](const string_pair& p){
table.add_row({p.first, p.second});
});
table.print(std::cout);
}
} }
} }

View file

@ -17,7 +17,9 @@
namespace gdpm::rest_api{ namespace gdpm::rest_api{
request_params make_from_config(const config::context& config){ request_params make_from_config(const config::context& config){
return config.rest_api_params; request_params rp = config.rest_api_params;
rp.verbose = config.verbose;
return rp;
} }
request_params make_request_params( request_params make_request_params(
@ -212,7 +214,7 @@ namespace gdpm::rest_api{
http_params.headers.insert(http::header("Connection", "keep-alive")); http_params.headers.insert(http::header("Connection", "keep-alive"));
string request_url = _prepare_request(url, c); string request_url = _prepare_request(url, c);
http::response r = http.request_get(request_url, http_params); http::response r = http.request_get(request_url, http_params);
if(c.verbose > 0) if(c.verbose >= log::INFO)
log::info("get_asset().URL: {}", request_url); log::info("get_asset().URL: {}", request_url);
return _parse_json(r.body, c.verbose); return _parse_json(r.body, c.verbose);
} }

View file

@ -186,7 +186,7 @@ namespace gdpm::utils{
int verbose int verbose
){ ){
constexpr const char *prog = "gpdm"; constexpr const char *prog = "gpdm";
struct zip *za; struct zip *zip;
struct zip_file *zf; struct zip_file *zf;
struct zip_stat sb; struct zip_stat sb;
char buf[100]; char buf[100];
@ -196,14 +196,14 @@ namespace gdpm::utils{
// log::info_n("Extracting package contents to '{}'...", dest); // log::info_n("Extracting package contents to '{}'...", dest);
log::info_n("Extracting package contents..."); log::info_n("Extracting package contents...");
if((za = zip_open(archive, 0, &err)) == nullptr){ if((zip = zip_open(archive, 0, &err)) == nullptr){
zip_error_to_str(buf, sizeof(buf), err, errno); zip_error_to_str(buf, sizeof(buf), err, errno);
log::error("{}: can't open zip archive {}: {}", prog, archive, buf); log::error("{}: can't open zip archive {}: {}", prog, archive, buf);
return 1; return 1;
} }
for(i = 0; i < zip_get_num_entries(za, 0); i++){ for(i = 0; i < zip_get_num_entries(zip, 0); i++){
if(zip_stat_index(za, i, 0, &sb) == 0){ if(zip_stat_index(zip, i, 0, &sb) == 0){
len = strlen(sb.name); len = strlen(sb.name);
if(verbose > 1){ if(verbose > 1){
log::print("{}, ", sb.name); log::print("{}, ", sb.name);
@ -215,7 +215,7 @@ namespace gdpm::utils{
// safe_create_dir(sb.name); // safe_create_dir(sb.name);
std::filesystem::create_directory(path); std::filesystem::create_directory(path);
} else { } else {
zf = zip_fopen_index(za, i, 0); zf = zip_fopen_index(zip, i, 0);
if(!zf){ if(!zf){
log::error("extract_zip: zip_fopen_index() failed."); log::error("extract_zip: zip_fopen_index() failed.");
return 100; return 100;
@ -248,7 +248,7 @@ namespace gdpm::utils{
} }
} }
if(zip_close(za) == -1){ if(zip_close(zip) == -1){
log::error("{}: can't close zip archive '{}'", prog, archive); log::error("{}: can't close zip archive '{}'", prog, archive);
return 1; return 1;
} }

View file

@ -22,7 +22,7 @@ TEST_SUITE("Command functions"){
using namespace gdpm::package_manager; using namespace gdpm::package_manager;
package::params params = package::params{ package::params params = package::params{
.remote_source = "test" .remote_source = "test",
}; };
config::context config = config::context{ config::context config = config::context{
.username = "", .username = "",
@ -33,9 +33,10 @@ TEST_SUITE("Command functions"){
.remote_sources = { .remote_sources = {
{"test", "http://godotengine.org/asset-library/api"} {"test", "http://godotengine.org/asset-library/api"}
}, },
.skip_prompt = true,
.info { .info {
.godot_version = "latest", .godot_version = "latest",
} },
}; };
package::title_list package_titles{"ResolutionManagerPlugin","godot-hmac", "Godot"}; package::title_list package_titles{"ResolutionManagerPlugin","godot-hmac", "Godot"};