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"]
path = modules/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_BUILD_RPATH "build/cmake")
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
"include"

View file

@ -17,6 +17,11 @@ namespace gdpm::package{
}
namespace gdpm::config{
enum class print_style{
list = 0,
table = 1,
};
struct context{
string username;
string password;
@ -25,24 +30,28 @@ namespace gdpm::config{
string packages_dir;
string tmp_dir;
string_map remote_sources;
size_t jobs = 1;
size_t timeout = 3000;
size_t max_results = 200;
int jobs = 1;
int timeout = 3000;
bool enable_sync = true;
bool enable_cache = true;
bool skip_prompt = false;
bool enable_file_logging;
bool clean_temporary;
int verbose;
int verbose = log::INFO;
print_style style = print_style::list;
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);
error load(std::filesystem::path path, 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);
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);
void print_json(const context& config);
void print_properties(const context& config, const string_list& properties);

View file

@ -13,6 +13,8 @@ namespace gdpm::constants::error{
NONE = 0,
UNKNOWN,
UNKNOWN_COMMAND,
UNKNOWN_ARGUMENT,
ARGPARSE_ERROR,
NOT_FOUND,
NOT_DEFINED,
NOT_IMPLEMENTED,
@ -102,6 +104,11 @@ namespace gdpm{
#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){
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_error_prefix() { return "[ERROR {}] "; }
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){
fmt::vprint(format, args);
@ -142,6 +143,21 @@ namespace gdpm::log
#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>
static constexpr void print(const S& format, Args&&...args){
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 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<title_list> get_package_titles(const info_list& packages);
GDPM_DLL_EXPORT void clean_temporary(const config::context& config, const title_list& package_titles);

View file

@ -1,11 +1,13 @@
#pragma once
#include "clipp.h"
#include <tuple>
#include <functional>
#include <type_traits>
#include <string>
#include <variant>
#include <future>
#include <any>
namespace gdpm{
class error;
@ -47,6 +49,7 @@ namespace gdpm{
using string_list = std::vector<string>;
using string_map = std::unordered_map<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>;
template <typename T = var>
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"
// RapidJSON
#include <any>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/rapidjson.h>
#include <rapidjson/writer.h>
@ -14,6 +15,7 @@
#include <rapidjson/prettywriter.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <tabulate/table.hpp>
// fmt
@ -67,7 +69,7 @@ namespace gdpm::config{
file.open(path, std::ios::in);
if(!file){
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();
save(config.path, config);
return error();
@ -230,6 +232,46 @@ namespace gdpm::config{
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(
const string& username,
@ -240,8 +282,8 @@ namespace gdpm::config{
const string& packages_dir,
const string& tmp_dir,
const string_map& remote_sources,
size_t threads,
size_t timeout,
int jobs,
int timeout,
bool enable_sync,
bool enable_file_logging,
int verbose
@ -254,7 +296,7 @@ namespace gdpm::config{
.packages_dir = (packages_dir.empty()) ? string(getenv("HOME")) + ".gdpm" : packages_dir,
.tmp_dir = tmp_dir,
.remote_sources = remote_sources,
.jobs = threads,
.jobs = jobs,
.timeout = timeout,
.enable_sync = enable_sync,
.enable_file_logging = enable_file_logging,
@ -293,6 +335,8 @@ namespace gdpm::config{
const context& config,
const string& property
){
using namespace tabulate;
if(property.empty()) return;
else if(property == "username") log::println("username: {}", config.username);
else if(property == "password") log::println("password: {}", config.password);
@ -311,10 +355,37 @@ namespace gdpm::config{
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(
const context& config,
const string_list& properties
){
using namespace tabulate;
if(config.style == config::print_style::list){
if(properties.empty()){
_print_property(config, "username");
_print_property(config, "password");
@ -332,6 +403,7 @@ namespace gdpm::config{
_print_property(config, "clean");
_print_property(config, "verbose");
}
else {
std::for_each(
properties.begin(),
properties.end(),
@ -340,5 +412,46 @@ namespace gdpm::config{
}
);
}
}
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/ostreamwrapper.h>
#include <rapidjson/prettywriter.h>
#include <tabulate/table.hpp>
namespace gdpm::package{
@ -39,8 +40,7 @@ namespace gdpm::package{
*/
/* Append files from --file option */
if(!params.input_files.empty()){
log::print("input files");
// if(!params.input_files.empty()){
for(const auto& filepath : params.input_files){
string contents = utils::readfile(filepath);
log::print("contents: {}", contents);
@ -51,7 +51,7 @@ namespace gdpm::package{
std::end(input_titles)
);
}
}
// }
result_t result = cache::get_package_info_by_title(package_titles);
package::info_list p_found = {};
package::info_list p_cache = result.unwrap_unsafe();
@ -445,16 +445,14 @@ namespace gdpm::package{
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;
Document doc = rest_api::get_assets_list(request_url, rest_api_params);
if(doc.IsNull()){
error error(
return log::error_rc(error(
constants::error::HOST_UNREACHABLE,
"Could not fetch metadata."
);
log::error(error);
return error;
));
}
// log::info("{} package(s) found...", doc["total_items"].GetInt());
@ -476,18 +474,21 @@ namespace gdpm::package{
result_t r_installed = cache::get_installed_packages();
info_list p_installed = r_installed.unwrap_unsafe();
if(!p_installed.empty()){
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"){
remote::print_repositories(config);
}
else{
error error(
log::error(error(
constants::error::UNKNOWN_COMMAND,
"Unrecognized subcommand. Try either 'packages' or 'remote' instead."
);
log::error(error);
));
}
return error();
}
@ -520,7 +521,7 @@ namespace gdpm::package{
}
}
std::ofstream of(path);
log::println("writing contents to file");
log::println("export: {}", path);
of << output;
of.close();
}
@ -532,17 +533,15 @@ namespace gdpm::package{
error link(
const config::context& config,
const title_list& package_titles,
const package::params& params /* path is last arg */
const package::params& params
){
using namespace std::filesystem;
if(params.args.empty()){
error error(
constants::error::INVALID_ARG_COUNT,
"Must supply at least 2 arguments (package name and path)"
);
log::error(error);
return error;
if(params.paths.empty()){
return log::error_rc(error(
constants::error::PATH_NOT_DEFINED,
"Path is required"
));
}
/* Check for packages in cache to link */
@ -550,12 +549,10 @@ namespace gdpm::package{
info_list p_found = {};
info_list p_cache = r_cache.unwrap_unsafe();
if(p_cache.empty()){
error error(
return log::error_rc(error(
constants::error::NOT_FOUND,
"Could not find any packages to link in cache."
);
log::error(error);
return error;
));
}
for(const auto& p_title : package_titles){
@ -575,17 +572,15 @@ 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){
const string _path = path;
log::info_n("link: \"{}\" -> '{}'...", p.title, _path + "/" + p.title);
for(const auto& path : params.paths){
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){
@ -609,13 +604,11 @@ namespace gdpm::package{
){
using namespace std::filesystem;
if(params.args.empty()){
error error(
constants::error::INVALID_ARG_COUNT,
"Must supply at least 2 arguments (package name and path)"
);
log::error(error);
return error;
if(params.paths.empty()){
return log::error_rc(error(
constants::error::PATH_NOT_DEFINED,
"Path is required"
));
}
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 */
if(p_cache.empty()){
error error(
return log::error_rc(error(
constants::error::NO_PACKAGE_FOUND,
"Could not find any packages to clone in cache."
);
log::error(error);
return error;
));
}
for(const auto& p_title : package_titles){
@ -694,7 +685,6 @@ namespace gdpm::package{
}
}
void print_list(const rapidjson::Document& json){
for(const auto& o : json["result"].GetArray()){
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){
return cache::get_package_info_by_title(package_titles);

View file

@ -22,9 +22,11 @@
#include <rapidjson/document.h>
#include <cxxopts.hpp>
#include "clipp.h"
#include "argparse/argparse.hpp"
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/prettywriter.h>
#include <stdexcept>
#include <system_error>
#include <future>
@ -79,185 +81,426 @@ namespace gdpm::package_manager{
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){
using namespace clipp;
using namespace argparse;
/* Replace cxxopts with clipp */
action_e action = action_e::none;
package::title_list package_titles;
package::params params;
auto doc_format = clipp::doc_formatting{}
.first_column(7)
.doc_column(45)
.last_column(99);
ArgumentParser program(argv[0], "0.0.1", argparse::default_arguments::help);
ArgumentParser install_command("install");
ArgumentParser add_command("add");
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 */
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 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";
ArgumentParser config_get("get");
ArgumentParser config_set("set");
/* Set the options */
// auto fileOpt = repeatable(option("--file", "-f").set(params.input_files) % "read file as input");
auto fileOpt = repeatable(option("--file", "-f") & values("input", params.input_files)) % "read file as input";
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";
ArgumentParser remote_add("add");
ArgumentParser remote_remove("remove");
ArgumentParser remote_list("list");
auto packageValues = values("packages", package_titles);
auto requiredPath = required("--path", params.args);
program.add_description("Manage Godot engine assets from CLI");
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" % (
command("install").set(action, action_e::install),
packageValues % "package(s) to install from asset library",
godotVersionOpt, cleanOpt, parallelOpt, syncOpt, skipOpt, remoteOpt, fileOpt
);
auto addCmd = "add" % (
command("add").set(action, action_e::add),
packageValues % "package(s) to add to project",
parallelOpt, skipOpt, remoteOpt, fileOpt
);
auto removeCmd = "remove" % (
command("remove").set(action, action_e::remove),
packageValues % "packages(s) to remove from project",
fileOpt, skipOpt, cleanOpt
);
auto updateCmd = "update package(s)" % (
command("update").set(action, action_e::update),
packageValues % ""
);
auto searchCmd = "search for package(s)" % (
command("search").set(action, action_e::search),
packageValues % "",
godotVersionOpt, fileOpt, remoteOpt, configOpt
);
auto exportCmd = "export installed package list to file" % (
command("export").set(action, action_e::p_export),
values("paths", params.args) % ""
);
auto listCmd = "show installed packages" % (
command("list").set(action, action_e::list)
);
auto linkCmd = "create link from package to project" % (
command("link").set(action, action_e::link),
value("package", package_titles) % "",
value("path", params.args) % ""
);
auto cloneCmd = "clone package to project" % (
command("clone").set(action, action_e::clone),
value("package", package_titles) % "",
value("path", params.args) % ""
);
auto cleanCmd = "clean temporary download files" % (
command("clean").set(action, action_e::clean),
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)
);
install_command.add_description("install package(s)");
install_command.add_argument("packages")
.required()
.nargs(nargs_pattern::at_least_one)
.help("packages to install");
install_command.add_argument("--godot-version")
.help("set Godot version for request");
install_command.add_argument("--clean")
.help("clean temporary files")
.implicit_value(true)
.default_value(false)
.nargs(0);
install_command.add_argument("--disable-sync")
.help("enable syncing with remote before installing")
.implicit_value(true)
.default_value(false)
.nargs(0);
install_command.add_argument("--disable-cache")
.help("disable caching asset data")
.implicit_value(true)
.default_value(false)
.nargs(0);
install_command.add_argument("--remote")
.help("set the remote to use")
.nargs(1);
install_command.add_argument("-j", "--jobs")
.help("set the number of parallel downloads")
.default_value(1)
.nargs(1)
.scan<'i', int>();
install_command.add_argument("-y", "--skip-prompt")
.help("skip the yes/no prompt")
.implicit_value(true)
.default_value(false)
.nargs(0);
install_command.add_argument("-f", "--file")
.help("set the file(s) to read as input")
.append()
.nargs(1);
install_command.add_argument("-t", "--timeout")
.help("set the request timeout")
.default_value(30)
.nargs(0);
auto cli = (
debugOpt, verboseOpt, configOpt,
(installCmd | addCmd | removeCmd | updateCmd | searchCmd | exportCmd |
listCmd | linkCmd | cloneCmd | cleanCmd | configCmd | fetchCmd |
remoteCmd | uiCmd | helpCmd | versionCmd)
);
add_command.add_description("add package to project");
add_command.add_argument("packages").nargs(nargs_pattern::at_least_one);
add_command.add_argument("--remote");
add_command.add_argument("-j", "--jobs")
.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 */
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(),
[&man_page_format](const man_page::section& s){
man_page_format += s.title() + "\n";
man_page_format += s.content() + "\n\n";
remove_command.add_description("remove package(s)");
remove_command.add_argument("packages").nargs(nargs_pattern::at_least_one);
remove_command.add_argument("--clean");
remove_command.add_argument("-y", "--skip-prompt");
remove_command.add_argument("-f", "--file")
.help("set the file(s) to read as input")
.append()
.nargs(nargs_pattern::at_least_one);
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)){
action = action_e::link;
package_titles = get_packages_from_parser(link_command);
set_if_used(link_command, params.paths, "path");
}
else if(program.is_subcommand_used(clone_command)){
action = action_e::clone;
package_titles = get_packages_from_parser(clone_command);
set_if_used(clone_command, params.paths, "path");
}
else if(program.is_subcommand_used(clean_command)){
action = action_e::clean;
package_titles = get_packages_from_parser(clean_command);
}
else if(program.is_subcommand_used(config_command)){
if(config_command.is_subcommand_used(config_get)){
action = action_e::config_get;
if(config_get.is_used("properties"))
params.args = config_get.get<string_list>("properties");
}
else if(config_command.is_subcommand_used(config_set)){
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;
}
);
// 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: package::add(config, package_titles);
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.args); 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::handle_config(config, package_titles, params.opts); 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 yet"); break;
case action_e::help: log::println("{}", man_page_format); 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: /* ...here to run with no command */ break;
}
} else {
log::println("usage:\n{}", usage_lines(cli, argv[0]).str());
case action_e::none: program.usage(); break;/* ...here to run with no command */ break;
}
return error();
}

View file

@ -1,9 +1,11 @@
#include "remote.hpp"
#include "config.hpp"
#include "error.hpp"
#include "log.hpp"
#include "types.hpp"
#include <readline/readline.h>
#include <tabulate/table.hpp>
namespace gdpm::remote{
@ -12,7 +14,7 @@ namespace gdpm::remote{
const args_t &args
){
/* 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){
return error(
constants::error::INVALID_ARG_COUNT,
@ -31,7 +33,7 @@ namespace gdpm::remote{
config::context& config,
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){
return error(
constants::error::INVALID_ARG_COUNT,
@ -61,8 +63,19 @@ namespace gdpm::remote{
void print_repositories(const config::context& config){
const auto &rs = config.remote_sources;
if(config.style == config::print_style::list){
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{
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(
@ -212,7 +214,7 @@ namespace gdpm::rest_api{
http_params.headers.insert(http::header("Connection", "keep-alive"));
string request_url = _prepare_request(url, c);
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);
return _parse_json(r.body, c.verbose);
}

View file

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

View file

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