Added CI script, updated dependencies, and fixed bugs

-Added CI yaml file for workflows
-Added `doctest` as dependency
-Added test target executable to CMakeLists.txt
-Renamed `bin` scripts to remove `gdpm*` prefix
-Updated `SQLite 3` dependency in CMakeLists.txt
-Implement basic unit testing in `tests/basic.cpp`
-Fixed issue with handling `fmt` strings
This commit is contained in:
David Allen 2023-01-04 20:49:00 -06:00
parent e36f0aee79
commit e048a762b2
22 changed files with 169 additions and 38 deletions

11
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,11 @@
name: github-ci
on: [push]
jobs:
build:
steps:
- uses: actions/checkout@v1

3
.gitignore vendored
View file

@ -1,5 +1,8 @@
build/** build/**
builds/**
bin/gdpm bin/gdpm
bin/gdpm-tests
cache/**
tests tests
*/tmp/** */tmp/**
vgcore.* vgcore.*

2
.gitlab-ci-local/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

45
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,45 @@
variables:
PROJECT_ROOT_PATH: /home/$USER/gdpm
stages:
- build
- test
- package
cache:
paths:
- .cache
before-script:
- echo "Setting up dependencies..."
- git clone $
build-job:
stage: build
script:
- echo -e "Building executable and libraries...\n$PWD"
- bin/compile.sh
test-job:
stage: test
script:
- echo "Running unit tests..."
- bin/gdpm-tests
package-job:
stage: package
script:
- echo "Packaging binaries..."
deploy-job:
stage: deploy
script:
- echo "Deploying application..."
environment: production

17
.travis.yml Normal file
View file

@ -0,0 +1,17 @@
os: linux
language: cpp
compiler: clang
env:
- ARCH_TRAVIS_VERBOSE=1
archlinux:
repos:
packages:
- base-devel
- git
- yay
script:
- "bin/compile.sh"

View file

@ -1,6 +1,6 @@
version: 0.1 version: 0.1
cli: cli:
version: 0.15.1-beta version: 1.2.1
lint: lint:
enabled: enabled:
- git-diff-check@SYSTEM - git-diff-check@SYSTEM

View file

@ -13,6 +13,7 @@ endif()
# Get source files except for main.cpp # Get source files except for main.cpp
file(GLOB SRC CONFIG_DEPENDS "src/[!main]*.cpp") file(GLOB SRC CONFIG_DEPENDS "src/[!main]*.cpp")
file(GLOB TESTS CONFIG_DEPENDS "tests/*.cpp")
# Find all the packages required to build # Find all the packages required to build
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
@ -22,24 +23,24 @@ find_package(Catch2 CONFIG REQUIRED)
find_package(cxxopts CONFIG REQUIRED) find_package(cxxopts CONFIG REQUIRED)
find_package(Poco CONFIG REQUIRED COMPONENTS Net JSON Util) find_package(Poco CONFIG REQUIRED COMPONENTS Net JSON Util)
find_package(libzip CONFIG REQUIRED) find_package(libzip CONFIG REQUIRED)
find_package(unofficial-sqlite3 CONFIG REQUIRED) # ...used with vcpkg unsuccessfully... 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-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 -pedantic-errors"
) )
set(INCLUDE_DIRS set(INCLUDE_DIRS
"include" "include"
"/usr/include/doctest"
${RAPIDJSON_INCLUDE_DIRS} ${RAPIDJSON_INCLUDE_DIRS}
${SQLite3_INCLUDE_DIRS}
) )
set(LINK_LIBS set(LINK_LIBS
fmt::fmt fmt::fmt
Threads::Threads Threads::Threads
Catch2::Catch2 Catch2::Catch2
cxxopts::cxxopts cxxopts::cxxopts
unofficial::sqlite3::sqlite3 SQLiteCpp
-lcurlpp -lcurlpp
-lzip -lzip
) )
@ -48,16 +49,19 @@ set(LINK_LIBS
add_library(${PROJECT_NAME}-shared SHARED "${SRC}") add_library(${PROJECT_NAME}-shared SHARED "${SRC}")
add_library(${PROJECT_NAME}-static STATIC "${SRC}") add_library(${PROJECT_NAME}-static STATIC "${SRC}")
add_executable(${PROJECT_NAME} "src/main.cpp") add_executable(${PROJECT_NAME} "src/main.cpp")
add_executable(${PROJECT_NAME}-tests "${TESTS}")
# Set include directories for targets # Set include directories for targets
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME}-shared PRIVATE ${INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME}-shared PRIVATE ${INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME}-static PRIVATE ${INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME}-static PRIVATE ${INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME}-tests PRIVATE ${INCLUDE_DIRS})
# Set link libraries for targets # Set link libraries for targets
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS}) target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS})
target_link_libraries(${PROJECT_NAME}-shared PRIVATE ${LINK_LIBS}) target_link_libraries(${PROJECT_NAME}-shared PRIVATE ${LINK_LIBS})
target_link_libraries(${PROJECT_NAME}-static PRIVATE ${LINK_LIBS}) target_link_libraries(${PROJECT_NAME}-static PRIVATE ${LINK_LIBS})
target_link_libraries(${PROJECT_NAME}-tests PRIVATE ${LINK_LIBS})
# Add project unit tests # Add project unit tests
# add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS}) # add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS})

16
bin/compile.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/sh
# Run this script at project root
#meson configure build
#CXX=clang++ meson compile -C build -j$(proc)
# CMake/ninja build system
mkdir -p build
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -G Ninja
ninja -C build
# Create symlinks to executables in build folder if necessary
ln -s ../build/gdpm bin/gdpm
ln -s ../build/gdpm-tests bin/gdpm-tests

View file

@ -1,3 +0,0 @@
# Run this script at project root
meson configure build
CXX=clang++ meson compile -C build -j$(proc)

View file

@ -1,4 +1,6 @@
#pragma once
#include "constants.hpp" #include "constants.hpp"
#include <sqlite3.h> #include <sqlite3.h>
#include <vector> #include <vector>

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "constants.hpp" #include "constants.hpp"
#include "error.hpp"
#include <string> #include <string>
#include <filesystem> #include <filesystem>
@ -9,7 +10,7 @@
namespace gdpm::config{ namespace gdpm::config{
struct config_context{ struct context{
std::string username; std::string username;
std::string password; std::string password;
std::string path; std::string path;
@ -24,10 +25,11 @@ namespace gdpm::config{
bool enable_file_logging; bool enable_file_logging;
int verbose; int verbose;
}; };
std::string to_json(const config_context& params); std::string to_json(const context& params);
config_context load(std::filesystem::path path, int verbose = 0); context load(std::filesystem::path path, int verbose = 0);
int save(const config_context& config, int verbose = 0); gdpm::error load(std::filesystem:: path, context& config, int verbose = 0);
config_context make_context(const std::string& username = GDPM_CONFIG_USERNAME, const std::string& password = GDPM_CONFIG_PASSWORD, const std::string& path = GDPM_CONFIG_PATH, const std::string& token = GDPM_CONFIG_TOKEN, const std::string& godot_version = GDPM_CONFIG_GODOT_VERSION, const std::string& packages_dir = GDPM_CONFIG_LOCAL_PACKAGES_DIR, const std::string& tmp_dir = GDPM_CONFIG_LOCAL_TMP_DIR, const std::set<std::string>& 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); gdpm::error save(std::filesystem::path path, const context& config, int verbose = 0);
context make_context(const std::string& username = GDPM_CONFIG_USERNAME, const std::string& password = GDPM_CONFIG_PASSWORD, const std::string& path = GDPM_CONFIG_PATH, const std::string& token = GDPM_CONFIG_TOKEN, const std::string& godot_version = GDPM_CONFIG_GODOT_VERSION, const std::string& packages_dir = GDPM_CONFIG_LOCAL_PACKAGES_DIR, const std::string& tmp_dir = GDPM_CONFIG_LOCAL_TMP_DIR, const std::set<std::string>& 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);
extern config_context config; extern context config;
} }

View file

@ -6,6 +6,7 @@
namespace gdpm::constants{ namespace gdpm::constants{
const std::string HomePath(std::string(std::getenv("HOME")) + "/"); const std::string HomePath(std::string(std::getenv("HOME")) + "/");
const std::string TestPath(HomePath + ".config/gdpm/tests")
const std::string ConfigPath(HomePath + ".config/gdpm/config.json"); const std::string ConfigPath(HomePath + ".config/gdpm/config.json");
const std::string LockfilePath(HomePath + ".config/gdpm/gdpm.lck"); const std::string LockfilePath(HomePath + ".config/gdpm/gdpm.lck");
const std::string LocalPackagesDir(HomePath + ".config/gdpm/packages"); const std::string LocalPackagesDir(HomePath + ".config/gdpm/packages");

16
include/error.hpp Normal file
View file

@ -0,0 +1,16 @@
#include <string>
namespace gdpm{
class error{
public:
error(int code, const std::string& message):
m_code(code), m_message(message)
{}
private:
int m_code;
std::string m_message;
}
}

View file

@ -29,7 +29,8 @@ namespace gdpm::log
#if GDPM_LOG_LEVEL > 0 #if GDPM_LOG_LEVEL > 0
vlog( vlog(
fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...) // fmt::make_format_args<Args...>(args...)
fmt::make_format_args(args...)
); );
#endif #endif
} }
@ -38,7 +39,8 @@ namespace gdpm::log
static constexpr void info_n(const S& format, Args&&...args){ static constexpr void info_n(const S& format, Args&&...args){
vlog( vlog(
fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...) // fmt::make_format_args<Args...>(args...)
fmt::make_format_args(args...)
); );
} }
@ -47,7 +49,8 @@ namespace gdpm::log
#if GDPM_LOG_LEVEL > 1 #if GDPM_LOG_LEVEL > 1
vlog( vlog(
fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...) // fmt::make_format_args<Args...>(args...)
fmt::make_format_args(args...)
); );
#endif #endif
} }
@ -57,7 +60,8 @@ namespace gdpm::log
#if GDPM_LOG_LEVEL > 1 #if GDPM_LOG_LEVEL > 1
vlog( vlog(
fmt::format(GDPM_COLOR_LOG_DEBUG "[DEBUG {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format), fmt::format(GDPM_COLOR_LOG_DEBUG "[DEBUG {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...) // fmt::make_format_args<Args...>(args...)
fmt::make_format_args(args...)
); );
#endif #endif
} }
@ -66,7 +70,8 @@ namespace gdpm::log
static constexpr void print(const S& format, Args&&...args){ static constexpr void print(const S& format, Args&&...args){
vlog( vlog(
fmt::format("{}", format), fmt::format("{}", format),
fmt::make_args_checked<Args...>(format, args...) // fmt::make_format_args<Args...>(args...)
fmt::make_format_args(args...)
); );
} }
@ -74,7 +79,8 @@ namespace gdpm::log
static constexpr void println(const S& format, Args&&...args){ static constexpr void println(const S& format, Args&&...args){
vlog( vlog(
fmt::format("{}\n", format), fmt::format("{}\n", format),
fmt::make_args_checked<Args...>(format, args...) // fmt::make_format_args<Args...>(args...)
fmt::make_format_args(args...)
); );
} }

View file

@ -14,7 +14,7 @@ namespace gdpm::package_manager{
extern std::vector<std::string> repo_sources; extern std::vector<std::string> repo_sources;
extern CURL *curl; extern CURL *curl;
extern CURLcode res; extern CURLcode res;
extern config::config_context config; extern config::context config;
struct package_info{ struct package_info{
size_t asset_id; size_t asset_id;

View file

@ -9,6 +9,7 @@ deps = [
dependency('RapidJSON'), dependency('RapidJSON'),
dependency('fmt'), dependency('fmt'),
dependency('Catch2'), dependency('Catch2'),
dependency('doctest'),
dependency('cxxopts'), dependency('cxxopts'),
# dependency('curl'), # dependency('curl'),
dependency('curlpp'), dependency('curlpp'),

View file

@ -26,8 +26,8 @@
namespace gdpm::config{ namespace gdpm::config{
config_context config; context config;
std::string to_json(const config_context& params){ std::string to_json(const context& params){
auto _build_json_array = [](std::set<std::string> a){ auto _build_json_array = [](std::set<std::string> a){
std::string o{"["}; std::string o{"["};
for(const std::string& src : a) for(const std::string& src : a)
@ -57,7 +57,8 @@ namespace gdpm::config{
return json; return json;
} }
config_context load(std::filesystem::path path, int verbose){
context load(std::filesystem::path path, int verbose){
std::fstream file; std::fstream file;
file.open(path, std::ios::in); file.open(path, std::ios::in);
if(!file){ if(!file){
@ -75,7 +76,7 @@ namespace gdpm::config{
*/ */
using namespace rapidjson; using namespace rapidjson;
/* Read JSON fro config, parse, and check document. Must make sure that program does not crash here and use default config instead! */ /* Read JSON from config, parse, and check document. Must make sure that program does not crash here and use default config instead! */
std::string contents, line; std::string contents, line;
while(std::getline(file, line)) while(std::getline(file, line))
contents += line + "\n"; contents += line + "\n";
@ -99,7 +100,7 @@ namespace gdpm::config{
// if(!status){ // if(!status){
// log::error("config::load: Could not parse contents of file (Error: {}/{}).", GetParseError_En(status), doc.GetErrorOffset()); // log::error("config::load: Could not parse contents of file (Error: {}/{}).", GetParseError_En(status), doc.GetErrorOffset());
// return config_context(); // return context();
// } // }
/* Must check if keys exists first, then populate _config_params. */ /* Must check if keys exists first, then populate _config_params. */
@ -141,7 +142,8 @@ namespace gdpm::config{
return config; return config;
} }
int save(const config_context& config, int verbose){
int save(std::filesystem::path path, const context& config, int verbose){
using namespace rapidjson; using namespace rapidjson;
/* Build a JSON string to pass to document */ /* Build a JSON string to pass to document */
@ -152,7 +154,7 @@ namespace gdpm::config{
/* Dump JSON config to file */ /* Dump JSON config to file */
Document doc; Document doc;
doc.Parse(json.c_str()); doc.Parse(json.c_str());
std::ofstream ofs(config.path); std::ofstream ofs(path);
OStreamWrapper osw(ofs); OStreamWrapper osw(ofs);
PrettyWriter<OStreamWrapper> writer(osw); PrettyWriter<OStreamWrapper> writer(osw);
@ -161,8 +163,9 @@ namespace gdpm::config{
return 0; return 0;
} }
config_context make_context(const std::string& username, const std::string& password, const std::string& path, const std::string& token, const std::string& godot_version, const std::string& packages_dir, const std::string& tmp_dir, const std::set<std::string>& remote_sources, size_t threads, size_t timeout, bool enable_sync, bool enable_file_logging, int verbose){
config_context config { context make_context(const std::string& username, const std::string& password, const std::string& path, const std::string& token, const std::string& godot_version, const std::string& packages_dir, const std::string& tmp_dir, const std::set<std::string>& remote_sources, size_t threads, size_t timeout, bool enable_sync, bool enable_file_logging, int verbose){
context config {
.username = username, .username = username,
.password = password, .password = password,
.path = path, .path = path,

View file

@ -27,7 +27,7 @@ namespace gdpm::http{
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer);
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent); curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
res = curl_easy_perform(curl); res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
@ -61,7 +61,7 @@ namespace gdpm::http{
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer);
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent); curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
res = curl_easy_perform(curl); res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
@ -109,7 +109,7 @@ namespace gdpm::http{
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_stream); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_stream);
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent); curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
res = curl_easy_perform(curl); res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);

View file

@ -1,11 +1,11 @@
// Godot Package Manager (GPM) // Godot Package Manager (GPM)
#include "constants.hpp" #include "constants.hpp"
#include "log.hpp" #include "log.hpp"
#include "config.hpp" #include "config.hpp"
#include "package_manager.hpp" #include "package_manager.hpp"
int main(int argc, char **argv){ int main(int argc, char **argv){
gdpm::package_manager::initialize(argc, argv); gdpm::package_manager::initialize(argc, argv);
gdpm::package_manager::execute(); gdpm::package_manager::execute();

View file

@ -33,7 +33,7 @@ namespace gdpm::package_manager{
std::vector<std::string> repo_sources; std::vector<std::string> repo_sources;
CURL *curl; CURL *curl;
CURLcode res; CURLcode res;
config::config_context config; config::context config;
rest_api::rest_api_context params; rest_api::rest_api_context params;
command_e command; command_e command;
std::vector<std::string> packages; std::vector<std::string> packages;
@ -67,6 +67,7 @@ namespace gdpm::package_manager{
return 0; return 0;
} }
int execute(){ int execute(){
run_command(command, packages, opts); run_command(command, packages, opts);
if(clean_tmp_dir) if(clean_tmp_dir)
@ -118,8 +119,10 @@ namespace gdpm::package_manager{
} }
log::println("Packages to install: "); log::println("Packages to install: ");
for(const auto& p : p_found) for(const auto& p : p_found){
std::string output((p.is_installed) ? p.title + " (reinstall)" : p.title);
log::print(" {} ", (p.is_installed) ? p.title + " (reinstall)" : p.title); log::print(" {} ", (p.is_installed) ? p.title + " (reinstall)" : p.title);
}
log::println(""); log::println("");
if(!skip_prompt){ if(!skip_prompt){
@ -566,7 +569,7 @@ namespace gdpm::package_manager{
/* Parse command-line arguments using cxxopts */ /* Parse command-line arguments using cxxopts */
cxxopts::Options options( cxxopts::Options options(
argv[0], argv[0],
"Experimental package manager made for managing assets for the Godot game engine.\n" "Experimental package manager made for managing assets for the Godot game engine through the command-line.\n"
); );
options.allow_unrecognised_options(); options.allow_unrecognised_options();
options.custom_help("[COMMAND] [OPTIONS...]"); options.custom_help("[COMMAND] [OPTIONS...]");
@ -880,3 +883,5 @@ namespace gdpm::package_manager{
} }
} // namespace gdpm::package_manager } // namespace gdpm::package_manager