Improve command-line client
This commit is contained in:
parent
6f2c6cabc2
commit
484b3c8015
|
@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
|||
|
||||
# add project files
|
||||
set(HEADER_FILES)
|
||||
set(SRC_FILES main.cpp)
|
||||
set(TEST_HEADER_FILES)
|
||||
set(SRC_FILES main.cpp config.cpp)
|
||||
set(TEST_HEADER_FILES config.h)
|
||||
set(TEST_SRC_FILES tests/cppunit.cpp)
|
||||
|
||||
# meta data
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
#include "./config.h"
|
||||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/io/inifile.h>
|
||||
#include <c++utilities/io/nativefilestream.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace CppUtilities;
|
||||
|
||||
void ClientConfig::parse(const Argument &configFileArg, const Argument &instanceArg)
|
||||
{
|
||||
// parse connfig file
|
||||
path = configFileArg.firstValue();
|
||||
if (!path || !*path) {
|
||||
path = "/etc/buildservice" PROJECT_CONFIG_SUFFIX "/client.conf";
|
||||
}
|
||||
auto configFile = NativeFileStream();
|
||||
configFile.exceptions(std::ios_base::badbit | std::ios_base::failbit);
|
||||
configFile.open(path, std::ios_base::in);
|
||||
auto configIni = AdvancedIniFile();
|
||||
configIni.parse(configFile);
|
||||
configFile.close();
|
||||
|
||||
// read innstance
|
||||
if (instanceArg.isPresent()) {
|
||||
instance = instanceArg.values().front();
|
||||
}
|
||||
for (const auto §ion : configIni.sections) {
|
||||
if (!section.name.starts_with("instance/")) {
|
||||
continue;
|
||||
}
|
||||
if (!instance.empty() && instance != std::string_view(section.name.data() + 9, section.name.size() - 9)) {
|
||||
continue;
|
||||
}
|
||||
instance = section.name;
|
||||
if (const auto url = section.findField("url"); url != section.fieldEnd()) {
|
||||
this->url = std::move(url->value);
|
||||
} else {
|
||||
throw std::runtime_error("Config is invalid: No \"url\" specified within \"" % section.name + "\".");
|
||||
}
|
||||
if (const auto user = section.findField("user"); user != section.fieldEnd()) {
|
||||
this->userName = std::move(user->value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (url.empty()) {
|
||||
throw std::runtime_error("Config is invalid: Instance configuration insufficient.");
|
||||
}
|
||||
|
||||
// read user data
|
||||
if (userName.empty()) {
|
||||
return;
|
||||
}
|
||||
if (const auto userSection = configIni.findSection("user/" + userName); userSection != configIni.sectionEnd()) {
|
||||
if (const auto password = userSection->findField("password"); password != userSection->fieldEnd()) {
|
||||
this->password = std::move(password->value);
|
||||
} else {
|
||||
throw std::runtime_error("Config is invalid: No \"password\" specified within \"" % userSection->name + "\".");
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Config is invalid: User \"" % userName + "\" referenced in instance configuration not found.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef REPOMGR_CLIENT_H
|
||||
#define REPOMGR_CLIENT_H
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
struct ClientConfig {
|
||||
void parse(const CppUtilities::Argument &configFileArg, const CppUtilities::Argument &instanceArg);
|
||||
|
||||
const char *path = nullptr;
|
||||
std::string instance;
|
||||
std::string url;
|
||||
std::string userName;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
#endif // REPOMGR_CLIENT_H
|
383
cli/main.cpp
383
cli/main.cpp
|
@ -1,4 +1,4 @@
|
|||
#include "resources/config.h"
|
||||
#include "./config.h"
|
||||
|
||||
#include "../librepomgr/buildactions/buildaction.h"
|
||||
#include "../librepomgr/buildactions/buildactionmeta.h"
|
||||
|
@ -9,13 +9,16 @@
|
|||
#include "../libpkg/data/database.h"
|
||||
#include "../libpkg/data/package.h"
|
||||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <reflective_rapidjson/json/errorformatting.h>
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
#include <c++utilities/application/commandlineutils.h>
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/ansiescapecodes.h>
|
||||
#include <c++utilities/io/inifile.h>
|
||||
#include <c++utilities/io/nativefilestream.h>
|
||||
#include <c++utilities/misc/parseerror.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
|
@ -38,70 +41,7 @@ using namespace CppUtilities;
|
|||
using namespace CppUtilities::EscapeCodes;
|
||||
using namespace std;
|
||||
|
||||
struct ClientConfig {
|
||||
void parse(const Argument &configFileArg, const Argument &instanceArg);
|
||||
|
||||
const char *path = nullptr;
|
||||
std::string instance;
|
||||
std::string url;
|
||||
std::string userName;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
void ClientConfig::parse(const Argument &configFileArg, const Argument &instanceArg)
|
||||
{
|
||||
// parse connfig file
|
||||
path = configFileArg.firstValue();
|
||||
if (!path || !*path) {
|
||||
path = "/etc/buildservice" PROJECT_CONFIG_SUFFIX "/client.conf";
|
||||
}
|
||||
auto configFile = NativeFileStream();
|
||||
configFile.exceptions(std::ios_base::badbit | std::ios_base::failbit);
|
||||
configFile.open(path, std::ios_base::in);
|
||||
auto configIni = AdvancedIniFile();
|
||||
configIni.parse(configFile);
|
||||
configFile.close();
|
||||
|
||||
// read innstance
|
||||
if (instanceArg.isPresent()) {
|
||||
instance = instanceArg.values().front();
|
||||
}
|
||||
for (const auto §ion : configIni.sections) {
|
||||
if (!section.name.starts_with("instance/")) {
|
||||
continue;
|
||||
}
|
||||
if (!instance.empty() && instance != std::string_view(section.name.data() + 9, section.name.size() - 9)) {
|
||||
continue;
|
||||
}
|
||||
instance = section.name;
|
||||
if (const auto url = section.findField("url"); url != section.fieldEnd()) {
|
||||
this->url = std::move(url->value);
|
||||
} else {
|
||||
throw std::runtime_error("Config is invalid: No \"url\" specified within \"" % section.name + "\".");
|
||||
}
|
||||
if (const auto user = section.findField("user"); user != section.fieldEnd()) {
|
||||
this->userName = std::move(user->value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (url.empty()) {
|
||||
throw std::runtime_error("Config is invalid: Instance configuration insufficient.");
|
||||
}
|
||||
|
||||
// read user data
|
||||
if (userName.empty()) {
|
||||
return;
|
||||
}
|
||||
if (const auto userSection = configIni.findSection("user/" + userName); userSection != configIni.sectionEnd()) {
|
||||
if (const auto password = userSection->findField("password"); password != userSection->fieldEnd()) {
|
||||
this->password = std::move(password->value);
|
||||
} else {
|
||||
throw std::runtime_error("Config is invalid: No \"password\" specified within \"" % userSection->name + "\".");
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Config is invalid: User \"" % userName + "\" referenced in instance configuration not found.");
|
||||
}
|
||||
}
|
||||
// helpers for formatting output
|
||||
|
||||
static void configureColumnWidths(tabulate::Table &table)
|
||||
{
|
||||
|
@ -148,7 +88,10 @@ static void configureColumnWidths(tabulate::Table &table)
|
|||
|
||||
static void printPackageSearchResults(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData)
|
||||
{
|
||||
const auto packages = ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibPkg::PackageSearchResult>>(jsonData.data(), jsonData.size());
|
||||
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
||||
errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All;
|
||||
const auto packages
|
||||
= ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibPkg::PackageSearchResult>>(jsonData.data(), jsonData.size(), &errors);
|
||||
tabulate::Table t;
|
||||
t.format().hide_border();
|
||||
t.add_row({ "Arch", "Repo", "Name", "Version", "Description", "Build date" });
|
||||
|
@ -180,7 +123,9 @@ static std::string formatDependencies(const std::vector<LibPkg::Dependency> &dep
|
|||
|
||||
static void printPackageDetails(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData)
|
||||
{
|
||||
const auto packages = ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibPkg::Package>>(jsonData.data(), jsonData.size());
|
||||
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
||||
errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All;
|
||||
const auto packages = ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibPkg::Package>>(jsonData.data(), jsonData.size(), &errors);
|
||||
for (const auto &package : packages) {
|
||||
const auto *const pkg = &package;
|
||||
std::cout << TextAttribute::Bold << pkg->name << ' ' << pkg->version << TextAttribute::Reset << '\n';
|
||||
|
@ -229,7 +174,10 @@ static void printPackageDetails(const LibRepoMgr::WebClient::Response::body_type
|
|||
|
||||
static void printListOfBuildActions(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData)
|
||||
{
|
||||
const auto actions = ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibRepoMgr::BuildAction>>(jsonData.data(), jsonData.size());
|
||||
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
||||
errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All;
|
||||
auto actions = ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibRepoMgr::BuildAction>>(jsonData.data(), jsonData.size(), &errors);
|
||||
actions.sort([] (const auto &lhs, const auto &rhs) { return lhs.created < rhs.created; });
|
||||
const auto meta = LibRepoMgr::BuildActionMetaInfo();
|
||||
const auto unknown = std::string_view("?");
|
||||
auto t = tabulate::Table();
|
||||
|
@ -254,65 +202,85 @@ static void printListOfBuildActions(const LibRepoMgr::WebClient::Response::body_
|
|||
std::cout << t << std::endl;
|
||||
}
|
||||
|
||||
static std::string printTimeStamp(const DateTime timeStamp)
|
||||
static std::string formatTimeStamp(const DateTime timeStamp)
|
||||
{
|
||||
static const auto now = DateTime::gmtNow();
|
||||
return (now - timeStamp).toString(TimeSpanOutputFormat::WithMeasures, true) % ' ' % '('
|
||||
return (now - timeStamp).toString(TimeSpanOutputFormat::WithMeasures, true) % " ago ("
|
||||
% timeStamp.toString(DateTimeOutputFormat::DateAndTime, true)
|
||||
+ ')';
|
||||
}
|
||||
|
||||
static void printBuildAction(const LibRepoMgr::BuildAction &a, const LibRepoMgr::BuildActionMetaInfo &meta)
|
||||
{
|
||||
constexpr auto unknown = std::string_view("?");
|
||||
const auto typeInfo = meta.isTypeIdValid(a.type) ? &meta.typeInfoForId(a.type) : nullptr;
|
||||
const auto status = static_cast<std::size_t>(a.status) < meta.states.size() ? meta.states[static_cast<std::size_t>(a.status)].name : unknown;
|
||||
const auto result = static_cast<std::size_t>(a.result) < meta.results.size() ? meta.results[static_cast<std::size_t>(a.result)].name : unknown;
|
||||
const auto flags = typeInfo ? &typeInfo->flags : nullptr;
|
||||
const auto startAfter = a.startAfter | std::views::transform([](auto id) { return numberToString(id); });
|
||||
|
||||
std::cout << TextAttribute::Bold << "Build action " << a.id << TextAttribute::Reset << '\n';
|
||||
tabulate::Table t;
|
||||
t.format().hide_border();
|
||||
t.add_row({ "Task", a.taskName });
|
||||
t.add_row({ "Type", (typeInfo ? typeInfo->name : unknown).data() });
|
||||
t.add_row({ "Status", status.data() });
|
||||
t.add_row({ "Result", result.data() });
|
||||
if (std::holds_alternative<std::string>(a.resultData)) {
|
||||
t.add_row({ "Result data", std::get<std::string>(a.resultData) });
|
||||
} else {
|
||||
t.add_row({ "Result data", "(no output formatter for result output type implemented yet)" });
|
||||
}
|
||||
t.add_row({ "Created", formatTimeStamp(a.created) });
|
||||
t.add_row({ "Started", formatTimeStamp(a.started) });
|
||||
t.add_row({ "Finished", formatTimeStamp(a.finished) });
|
||||
t.add_row({ "Start after", joinStrings<decltype(startAfter), std::string>(startAfter, ", ") });
|
||||
t.add_row({ "Directory", a.directory });
|
||||
t.add_row({ "Source repo", joinStrings(a.sourceDbs, "\n") });
|
||||
t.add_row({ "Destination repo", joinStrings(a.destinationDbs, "\n") });
|
||||
t.add_row({ "Packages", joinStrings(a.packageNames, "\n") });
|
||||
if (flags) {
|
||||
auto presentFlags = std::string();
|
||||
presentFlags.reserve(32);
|
||||
for (const auto &flag : *flags) {
|
||||
if (a.flags & flag.id) {
|
||||
if (!presentFlags.empty()) {
|
||||
presentFlags += ", ";
|
||||
}
|
||||
presentFlags += flag.name;
|
||||
}
|
||||
}
|
||||
t.add_row({ "Flags", std::move(presentFlags) });
|
||||
} else {
|
||||
t.add_row({ "Flags", numberToString(a.flags) });
|
||||
}
|
||||
t.add_row({ "Output", a.output });
|
||||
t.column(0).format().font_align(tabulate::FontAlign::right);
|
||||
std::cout << t << '\n';
|
||||
}
|
||||
|
||||
static void printBuildAction(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData)
|
||||
{
|
||||
auto buildAction = LibRepoMgr::BuildAction();
|
||||
{
|
||||
const auto doc = ReflectiveRapidJSON::JsonReflector::parseJsonDocFromString(jsonData.data(), jsonData.size());
|
||||
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
||||
errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All;
|
||||
ReflectiveRapidJSON::JsonReflector::pull(buildAction, doc.GetObject(), &errors);
|
||||
}
|
||||
printBuildAction(buildAction, LibRepoMgr::BuildActionMetaInfo());
|
||||
}
|
||||
|
||||
static void printBuildActions(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData)
|
||||
{
|
||||
const auto buildActions = ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibRepoMgr::BuildAction>>(jsonData.data(), jsonData.size());
|
||||
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
|
||||
errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All;
|
||||
auto buildActions
|
||||
= ReflectiveRapidJSON::JsonReflector::fromJson<std::list<LibRepoMgr::BuildAction>>(jsonData.data(), jsonData.size(), &errors);
|
||||
buildActions.sort([] (const auto &lhs, const auto &rhs) { return lhs.created < rhs.created; });
|
||||
const auto meta = LibRepoMgr::BuildActionMetaInfo();
|
||||
const auto unknown = std::string_view("?");
|
||||
for (const auto &a : buildActions) {
|
||||
const auto typeInfo = meta.isTypeIdValid(a.type) ? &meta.typeInfoForId(a.type) : nullptr;
|
||||
const auto status = static_cast<std::size_t>(a.status) < meta.states.size() ? meta.states[static_cast<std::size_t>(a.status)].name : unknown;
|
||||
const auto result
|
||||
= static_cast<std::size_t>(a.result) < meta.results.size() ? meta.results[static_cast<std::size_t>(a.result)].name : unknown;
|
||||
const auto flags = typeInfo ? &typeInfo->flags : nullptr;
|
||||
const auto startAfter = a.startAfter | std::views::transform([](auto id) { return numberToString(id); });
|
||||
|
||||
std::cout << TextAttribute::Bold << "Build action " << a.id << TextAttribute::Reset << '\n';
|
||||
tabulate::Table t;
|
||||
t.format().hide_border();
|
||||
t.add_row({ "Task", a.taskName });
|
||||
t.add_row({ "Type", (typeInfo ? typeInfo->name : unknown).data() });
|
||||
t.add_row({ "Status", status.data() });
|
||||
t.add_row({ "Result", result.data() });
|
||||
if (std::holds_alternative<std::string>(a.resultData)) {
|
||||
t.add_row({ "Result data", std::get<std::string>(a.resultData) });
|
||||
} else {
|
||||
t.add_row({ "Result data", "(no output formatter for result output type implemented yet)" });
|
||||
}
|
||||
t.add_row({ "Created", printTimeStamp(a.created) });
|
||||
t.add_row({ "Started", printTimeStamp(a.started) });
|
||||
t.add_row({ "Finished", printTimeStamp(a.finished) });
|
||||
t.add_row({ "Start after", joinStrings<decltype(startAfter), std::string>(startAfter, ", ") });
|
||||
t.add_row({ "Directory", a.directory });
|
||||
t.add_row({ "Source repo", joinStrings(a.sourceDbs, "\n") });
|
||||
t.add_row({ "Destination repo", joinStrings(a.destinationDbs, "\n") });
|
||||
t.add_row({ "Packages", joinStrings(a.packageNames, "\n") });
|
||||
if (flags) {
|
||||
auto presentFlags = std::string();
|
||||
presentFlags.reserve(32);
|
||||
for (const auto &flag : *flags) {
|
||||
if (a.flags & flag.id) {
|
||||
if (!presentFlags.empty()) {
|
||||
presentFlags += ", ";
|
||||
}
|
||||
presentFlags += flag.name;
|
||||
}
|
||||
}
|
||||
t.add_row({ "Flags", std::move(presentFlags) });
|
||||
} else {
|
||||
t.add_row({ "Flags", numberToString(a.flags) });
|
||||
}
|
||||
t.add_row({ "Output", a.output });
|
||||
t.column(0).format().font_align(tabulate::FontAlign::right);
|
||||
std::cout << t << '\n';
|
||||
printBuildAction(a, meta);
|
||||
}
|
||||
std::cout.flush();
|
||||
}
|
||||
|
@ -320,7 +288,13 @@ static void printBuildActions(const LibRepoMgr::WebClient::Response::body_type::
|
|||
static void printRawDataForErrorHandling(const LibRepoMgr::WebClient::Response::body_type::value_type &rawData)
|
||||
{
|
||||
if (!rawData.empty()) {
|
||||
std::cerr << Phrases::InfoMessage << "Server replied:" << Phrases::End << rawData << '\n';
|
||||
std::cerr << Phrases::InfoMessage << "Server replied:";
|
||||
if (rawData.size() > 50 || rawData.find('\n') != std::string::npos) {
|
||||
std::cerr << Phrases::End;
|
||||
} else {
|
||||
std::cerr << TextAttribute::Reset << ' ';
|
||||
}
|
||||
std::cerr << rawData << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,22 +316,60 @@ static void handleResponse(const std::string &url, const LibRepoMgr::WebClient::
|
|||
return;
|
||||
}
|
||||
if (response.result() != boost::beast::http::status::ok) {
|
||||
std::cerr << Phrases::ErrorMessage << "HTTP request not successful: " << error.what() << Phrases::End;
|
||||
std::cerr << Phrases::ErrorMessage << "HTTP request not successful: " << response.result() << " ("
|
||||
<< static_cast<std::underlying_type_t<decltype(response.result())>>(response.result()) << " response)" << Phrases::End;
|
||||
std::cerr << Phrases::InfoMessage << "URL was: " << url << Phrases::End;
|
||||
printRawDataForErrorHandling(body);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
std::invoke(printer, body);
|
||||
} catch (const ReflectiveRapidJSON::JsonDeserializationError &e) {
|
||||
std::cerr << Phrases::ErrorMessage << "Unable to make sense of response: " << ReflectiveRapidJSON::formatJsonDeserializationError(e)
|
||||
<< Phrases::End;
|
||||
returnCode = 13;
|
||||
} catch (const RAPIDJSON_NAMESPACE::ParseResult &e) {
|
||||
std::cerr << Phrases::ErrorMessage << "Unable to parse responnse: " << tupleToString(LibRepoMgr::serializeParseError(e)) << Phrases::End;
|
||||
std::cerr << Phrases::InfoMessage << "URL was: " << url << std::endl;
|
||||
returnCode = 11;
|
||||
} catch (const std::runtime_error &e) {
|
||||
std::cerr << Phrases::ErrorMessage << "Unable to display response: " << e.what() << Phrases::End;
|
||||
std::cerr << Phrases::InfoMessage << "URL was: " << url << std::endl;
|
||||
returnCode = 12;
|
||||
}
|
||||
if (returnCode) {
|
||||
std::cerr << Phrases::InfoMessage << "URL was: " << url << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// helper for turning CLI args into URL query parameters
|
||||
|
||||
static std::string asQueryParam(const Argument &cliArg, std::string_view paramName = std::string_view())
|
||||
{
|
||||
if (!cliArg.isPresent()) {
|
||||
return std::string();
|
||||
}
|
||||
const auto argValues
|
||||
= cliArg.values(0) | std::views::transform([](const char *argValue) { return LibRepoMgr::WebAPI::Url::encodeValue(argValue); });
|
||||
return joinStrings<decltype(argValues), std::string>(
|
||||
argValues, "&", true, argsToString(paramName.empty() ? std::string_view(cliArg.name()) : paramName, '='));
|
||||
}
|
||||
|
||||
static void appendAsQueryParam(std::string &path, const Argument &cliArg, std::string_view paramName = std::string_view())
|
||||
{
|
||||
auto asParam = asQueryParam(cliArg, paramName);
|
||||
if (asParam.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!(path.empty() || path.ends_with('?'))) {
|
||||
path += '&';
|
||||
}
|
||||
path += std::move(asParam);
|
||||
}
|
||||
|
||||
static void appendAsQueryParam(std::string &path, std::initializer_list<const Argument *> args)
|
||||
{
|
||||
for (const auto *arg : args) {
|
||||
appendAsQueryParam(path, *arg, arg->name());
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
|
@ -368,82 +380,136 @@ int main(int argc, const char *argv[])
|
|||
void (*printer)(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) = nullptr;
|
||||
|
||||
// read CLI args
|
||||
ArgumentParser parser;
|
||||
ConfigValueArgument configFileArg("config-file", 'c', "specifies the path of the config file", { "path" });
|
||||
auto parser = ArgumentParser();
|
||||
auto configFileArg = ConfigValueArgument("config-file", 'c', "specifies the path of the config file", { "path" });
|
||||
configFileArg.setEnvironmentVariable(PROJECT_VARNAME_UPPER "_CONFIG_FILE");
|
||||
ConfigValueArgument instanceArg("instance", 'i', "specifies the instance to connect to", { "instance" });
|
||||
ConfigValueArgument rawArg("raw", 'r', "print the raw output from the server");
|
||||
OperationArgument packageArg("package", 'p', "Package-related operations:");
|
||||
OperationArgument searchArg("search", 's', "searches for packages");
|
||||
ConfigValueArgument searchTermArg("term", 't', "specifies the search term", { "term" });
|
||||
auto instanceArg = ConfigValueArgument("instance", 'i', "specifies the instance to connect to", { "instance" });
|
||||
auto rawArg = ConfigValueArgument("raw", 'r', "print the raw output from the server");
|
||||
auto verboseArg = ConfigValueArgument("verbose", 'v', "prints debugging output");
|
||||
auto packageArg = OperationArgument("package", 'p', "Package-related operations:");
|
||||
auto searchArg = OperationArgument("search", 's', "searches for packages");
|
||||
auto searchTermArg = ConfigValueArgument("term", 't', "specifies the search term", { "term" });
|
||||
searchTermArg.setImplicit(true);
|
||||
searchTermArg.setRequired(true);
|
||||
ConfigValueArgument searchModeArg("mode", 'm', "specifies the mode", { "name/name-contains/regex/provides/depends/libprovides/libdepends" });
|
||||
auto searchModeArg
|
||||
= ConfigValueArgument("mode", 'm', "specifies the mode", { "name/name-contains/regex/provides/depends/libprovides/libdepends" });
|
||||
searchModeArg.setPreDefinedCompletionValues("name name-contains regex provides depends libprovides libdepends");
|
||||
searchArg.setSubArguments({ &searchTermArg, &searchModeArg });
|
||||
searchArg.setCallback([&path, &printer, &searchTermArg, &searchModeArg](const ArgumentOccurrence &) {
|
||||
path = "/api/v0/packages?mode=" % LibRepoMgr::WebAPI::Url::encodeValue(searchModeArg.firstValueOr("name-contains")) % "&name="
|
||||
+ LibRepoMgr::WebAPI::Url::encodeValue(searchTermArg.values().front());
|
||||
path = "/api/v0/packages?mode=" + LibRepoMgr::WebAPI::Url::encodeValue(searchModeArg.firstValueOr("name-contains"));
|
||||
printer = printPackageSearchResults;
|
||||
appendAsQueryParam(path, searchTermArg, "name");
|
||||
});
|
||||
ConfigValueArgument packageNameArg("name", 'n', "specifies the package name", { "name" });
|
||||
auto packageNameArg = ConfigValueArgument("name", 'n', "specifies the package name", { "name" });
|
||||
packageNameArg.setImplicit(true);
|
||||
packageNameArg.setRequired(true);
|
||||
OperationArgument packageShowArg("show", 'd', "shows details about a package");
|
||||
auto packageShowArg = OperationArgument("show", 'd', "shows details about a package");
|
||||
packageShowArg.setSubArguments({ &packageNameArg });
|
||||
packageShowArg.setCallback([&path, &printer, &packageNameArg](const ArgumentOccurrence &) {
|
||||
path = "/api/v0/packages?mode=name&details=1&name=" + LibRepoMgr::WebAPI::Url::encodeValue(packageNameArg.values().front());
|
||||
path = "/api/v0/packages?mode=name&details=1";
|
||||
printer = printPackageDetails;
|
||||
appendAsQueryParam(path, packageNameArg, "name");
|
||||
});
|
||||
packageArg.setSubArguments({ &searchArg, &packageShowArg });
|
||||
OperationArgument actionArg("action", 'a', "Build-action-related operations:");
|
||||
OperationArgument listActionsArg("list", 'l', "list build actions");
|
||||
auto actionArg = OperationArgument("action", 'a', "Build-action-related operations:");
|
||||
auto listActionsArg = OperationArgument("list", 'l', "list build actions");
|
||||
listActionsArg.setCallback([&path, &printer](const ArgumentOccurrence &) {
|
||||
path = "/api/v0/build-action";
|
||||
printer = printListOfBuildActions;
|
||||
});
|
||||
ConfigValueArgument buildActionIdArg("id", '\0', "specifies the build action ID", { "ID" });
|
||||
auto buildActionIdArg = ConfigValueArgument("id", '\0', "specifies the build action IDs", { "ID" });
|
||||
buildActionIdArg.setImplicit(true);
|
||||
buildActionIdArg.setRequired(true);
|
||||
OperationArgument showBuildActionArg("show", 'd', "show details about a build action");
|
||||
buildActionIdArg.setRequiredValueCount(Argument::varValueCount);
|
||||
auto showBuildActionArg = OperationArgument("show", 'd', "show details about a build action");
|
||||
showBuildActionArg.setCallback([&path, &printer, &buildActionIdArg](const ArgumentOccurrence &) {
|
||||
path = "/api/v0/build-action/details?id=" + LibRepoMgr::WebAPI::Url::encodeValue(buildActionIdArg.firstValueOr("0"));
|
||||
path = "/api/v0/build-action/details?";
|
||||
printer = printBuildActions;
|
||||
appendAsQueryParam(path, buildActionIdArg, "id");
|
||||
});
|
||||
showBuildActionArg.setSubArguments({ &buildActionIdArg });
|
||||
OperationArgument deleteBuildActionArg("delete", '\0', "deletes a build action");
|
||||
auto createBuildActionArg = OperationArgument("create", '\0', "creates and starts a new build action (or pre-defined task)");
|
||||
auto taskArg = ConfigValueArgument("task", '\0', "specifies the pre-defined task to run", { "task" });
|
||||
auto typeArg = ConfigValueArgument("type", '\0', "specifies the action type", { "type" });
|
||||
auto directoryArg = ConfigValueArgument("directory", '\0', "specifies the directory", { "path" });
|
||||
auto startConditionArg = ConfigValueArgument("start-condition", '\0', "specifies the start condition", { "immediately/after/manually" });
|
||||
auto startAfterIdArg
|
||||
= ConfigValueArgument("start-after-id", '\0', "specifies the IDs of existing build actions to start the new action after", { "ID" });
|
||||
auto sourceRepoArg = ConfigValueArgument("source-repo", '\0', "specifies the source repositories", { "database-name" });
|
||||
auto destinationRepoArg = ConfigValueArgument("destination-repo", '\0', "specifies the destination repositories", { "database-name" });
|
||||
auto packagesArg = ConfigValueArgument("package", '\0', "specifies the packages", { "package-name" });
|
||||
startConditionArg.setPreDefinedCompletionValues("immediately after manually");
|
||||
startAfterIdArg.setRequiredValueCount(Argument::varValueCount);
|
||||
sourceRepoArg.setRequiredValueCount(Argument::varValueCount);
|
||||
destinationRepoArg.setRequiredValueCount(Argument::varValueCount);
|
||||
packagesArg.setRequiredValueCount(Argument::varValueCount);
|
||||
createBuildActionArg.setCallback([&verb, &path, &printer, &taskArg, &typeArg, &directoryArg, &startConditionArg, &startAfterIdArg, &sourceRepoArg,
|
||||
&destinationRepoArg, &packagesArg](const ArgumentOccurrence &) {
|
||||
verb = boost::beast::http::verb::post;
|
||||
path = "/api/v0/build-action?";
|
||||
printer = printBuildAction;
|
||||
if (taskArg.isPresent() && typeArg.isPresent()) {
|
||||
throw ParseError("The arguments --task and --type can not be combined.");
|
||||
}
|
||||
appendAsQueryParam(path,
|
||||
{ &(taskArg.isPresent() ? taskArg : typeArg), &directoryArg, &startConditionArg, &startAfterIdArg, &sourceRepoArg, &destinationRepoArg,
|
||||
&packagesArg });
|
||||
});
|
||||
createBuildActionArg.setSubArguments(
|
||||
{ &typeArg, &taskArg, &directoryArg, &startConditionArg, &startAfterIdArg, &sourceRepoArg, &destinationRepoArg, &packagesArg });
|
||||
auto deleteBuildActionArg = OperationArgument("delete", '\0', "deletes a build action");
|
||||
deleteBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) {
|
||||
verb = boost::beast::http::verb::delete_;
|
||||
path = "/api/v0/build-action?id=" + LibRepoMgr::WebAPI::Url::encodeValue(buildActionIdArg.firstValueOr("0"));
|
||||
path = "/api/v0/build-action?";
|
||||
printer = printRawData;
|
||||
appendAsQueryParam(path, buildActionIdArg, "id");
|
||||
});
|
||||
deleteBuildActionArg.setSubArguments({ &buildActionIdArg });
|
||||
OperationArgument cloneBuildActionArg("clone", '\0', "clones a build action");
|
||||
auto cloneBuildActionArg = OperationArgument("clone", '\0', "clones a build action");
|
||||
cloneBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) {
|
||||
verb = boost::beast::http::verb::post;
|
||||
path = "/api/v0/build-action/clone?id=" + LibRepoMgr::WebAPI::Url::encodeValue(buildActionIdArg.firstValueOr("0"));
|
||||
path = "/api/v0/build-action/clone?";
|
||||
printer = printRawData;
|
||||
appendAsQueryParam(path, buildActionIdArg, "id");
|
||||
});
|
||||
cloneBuildActionArg.setSubArguments({ &buildActionIdArg });
|
||||
OperationArgument startBuildActionArg("start", '\0', "starts a build action");
|
||||
auto startBuildActionArg = OperationArgument("start", '\0', "starts a build action");
|
||||
startBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) {
|
||||
verb = boost::beast::http::verb::post;
|
||||
path = "/api/v0/build-action/start?id=" + LibRepoMgr::WebAPI::Url::encodeValue(buildActionIdArg.firstValueOr("0"));
|
||||
path = "/api/v0/build-action/start?";
|
||||
printer = printRawData;
|
||||
appendAsQueryParam(path, buildActionIdArg, "id");
|
||||
});
|
||||
startBuildActionArg.setSubArguments({ &buildActionIdArg });
|
||||
OperationArgument stopBuildActionArg("stop", '\0', "stops a build action");
|
||||
auto stopBuildActionArg = OperationArgument("stop", '\0', "stops a build action");
|
||||
stopBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) {
|
||||
verb = boost::beast::http::verb::post;
|
||||
path = "/api/v0/build-action/stop?id=" + LibRepoMgr::WebAPI::Url::encodeValue(buildActionIdArg.firstValueOr("0"));
|
||||
path = "/api/v0/build-action/stop?";
|
||||
printer = printRawData;
|
||||
appendAsQueryParam(path, buildActionIdArg, "id");
|
||||
});
|
||||
stopBuildActionArg.setSubArguments({ &buildActionIdArg });
|
||||
actionArg.setSubArguments(
|
||||
{ &listActionsArg, &showBuildActionArg, &deleteBuildActionArg, &cloneBuildActionArg, &startBuildActionArg, &stopBuildActionArg });
|
||||
HelpArgument helpArg(parser);
|
||||
NoColorArgument noColorArg;
|
||||
parser.setMainArguments({ &packageArg, &actionArg, &instanceArg, &configFileArg, &rawArg, &noColorArg, &helpArg });
|
||||
actionArg.setSubArguments({ &listActionsArg, &showBuildActionArg, &createBuildActionArg, &deleteBuildActionArg, &cloneBuildActionArg,
|
||||
&startBuildActionArg, &stopBuildActionArg });
|
||||
auto apiArg = OperationArgument("api", '\0', "Invoke a generic API request:");
|
||||
auto pathArg = ConfigValueArgument("path", '\0', "specifies the route's path without prefix", { "path/of/route?foo=bar&bar=foo" });
|
||||
pathArg.setImplicit(true);
|
||||
pathArg.setRequired(true);
|
||||
auto methodArg = ConfigValueArgument("method", 'x', "specifies the method", { "GET/POST/PUT/DELETE" });
|
||||
methodArg.setPreDefinedCompletionValues("GET POST PUT DELETE");
|
||||
apiArg.setCallback([&verb, &path, &printer, &pathArg, &methodArg](const ArgumentOccurrence &) {
|
||||
const auto *rawVerb = methodArg.firstValueOr("GET");
|
||||
verb = boost::beast::http::string_to_verb(rawVerb);
|
||||
if (verb == boost::beast::http::verb::unknown) {
|
||||
throw ParseError(argsToString('\"', rawVerb, "\" is not a valid method."));
|
||||
}
|
||||
path = argsToString("/api/v0/", pathArg.values(0).front());
|
||||
printer = printRawData;
|
||||
});
|
||||
apiArg.setSubArguments({ &pathArg, &methodArg });
|
||||
auto helpArg = HelpArgument(parser);
|
||||
auto noColorArg = NoColorArgument();
|
||||
parser.setMainArguments({ &packageArg, &actionArg, &apiArg, &instanceArg, &configFileArg, &rawArg, &verboseArg, &noColorArg, &helpArg });
|
||||
parser.parseArgs(argc, argv);
|
||||
|
||||
// return early if no operation specified
|
||||
|
@ -458,6 +524,9 @@ int main(int argc, const char *argv[])
|
|||
auto config = ClientConfig();
|
||||
try {
|
||||
config.parse(configFileArg, instanceArg);
|
||||
if (verboseArg.isPresent()) {
|
||||
std::cerr << Phrases::InfoMessage << "Read config from: " << config.path << std::endl;
|
||||
}
|
||||
} catch (const std::runtime_error &e) {
|
||||
std::cerr << Phrases::ErrorMessage << "Unable to parse config: " << e.what() << Phrases::End;
|
||||
std::cerr << Phrases::InfoMessage << "Path of config file was: " << (config.path ? config.path : "[none]") << Phrases::End;
|
||||
|
@ -471,11 +540,13 @@ int main(int argc, const char *argv[])
|
|||
auto returnCode = 0;
|
||||
sslContext.set_verify_mode(boost::asio::ssl::verify_peer);
|
||||
sslContext.set_default_verify_paths();
|
||||
if (verboseArg.isPresent()) {
|
||||
std::cerr << Phrases::InfoMessage << verb << ':' << ' ' << url << std::endl;
|
||||
}
|
||||
LibRepoMgr::WebClient::runSessionFromUrl(ioContext, sslContext, url,
|
||||
std::bind(&handleResponse, std::ref(url), std::placeholders::_1, std::placeholders::_2, rawArg.isPresent() ? printRawData : printer,
|
||||
std::ref(returnCode)),
|
||||
std::string(), config.userName, config.password, verb);
|
||||
ioContext.run();
|
||||
|
||||
return 0;
|
||||
return returnCode;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue