chore: baseline usable version

This commit is contained in:
dela
2026-06-10 11:17:51 +08:00
commit a2f82a86b6
932 changed files with 172551 additions and 0 deletions
@@ -0,0 +1,79 @@
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
if(QMSETUP_STATIC_RUNTIME)
# To avoid a command execution that can't find the library, we choose to link compiler runtime statically
if(MINGW)
add_link_options(-static -static-libgcc -static-libstdc++)
elseif(MSVC)
add_compile_options(/MT)
endif()
endif()
# Get syscmdline
find_package(syscmdline QUIET)
if(NOT TARGET syscmdline::syscmdline)
set(SYSCMDLINE_INSTALL off)
add_subdirectory(syscmdline)
endif()
# Add subdirectories
add_subdirectory(corecmd)
# Add headers
set(_corelib_name library)
add_library(${_corelib_name} INTERFACE)
target_include_directories(${_corelib_name} INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${QMSETUP_INSTALL_NAME}>"
)
install(TARGETS ${_corelib_name}
EXPORT ${QMSETUP_INSTALL_NAME}Targets
)
if(TRUE)
# Add install target
set(_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${QMSETUP_INSTALL_NAME})
# Add version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}ConfigVersion.cmake"
VERSION ${QMSETUP_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Add configuration file
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake"
INSTALL_DESTINATION ${_install_dir}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install cmake files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}ConfigVersion.cmake"
DESTINATION ${_install_dir}
)
# Install cmake targets files
install(EXPORT ${QMSETUP_INSTALL_NAME}Targets
FILE "${QMSETUP_INSTALL_NAME}Targets.cmake"
NAMESPACE qmsetup::
DESTINATION ${_install_dir}
)
# Install headers
install(DIRECTORY ../include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
# Install cmake modules
install(DIRECTORY ../cmake/
DESTINATION ${_install_dir}/cmake
)
endif()
@@ -0,0 +1,89 @@
project(qmcorecmd VERSION ${QMSETUP_VERSION})
string(TIMESTAMP _year "%Y")
set(RC_COPYRIGHT "Copyright 2023-${_year} Stdware Collections")
set(RC_DESCRIPTION "QMSetup Core Utility Command")
set(_src
main.cpp
sha-256.h
sha-256.cpp
utils.h
utils.cpp
)
if(WIN32)
list(APPEND _src utils_win.cpp)
else()
list(APPEND _src utils_unix.cpp)
endif()
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline::syscmdline)
# Add features
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 cxx_std_17)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_EXTENSIONS OFF
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
# Compat with gcc 8
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9")
target_link_libraries(${PROJECT_NAME} PRIVATE stdc++fs)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi)
target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
if(WIN32)
qm_add_win_rc(${PROJECT_NAME}
COPYRIGHT ${RC_COPYRIGHT}
DESCRIPTION ${RC_DESCRIPTION}
)
# Make sure Windows UAC does not believe it is an installer
qm_add_win_manifest(${PROJECT_NAME}
DESCRIPTION ${RC_DESCRIPTION}
)
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_DESC="${RC_DESCRIPTION}, Version ${QMSETUP_VERSION}")
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_COPYRIGHT="${RC_COPYRIGHT}, checkout https://github.com/stdware/qmsetup")
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_VERSION="${QMSETUP_VERSION}")
set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME corecmd)
# Vcpkg requires the binary to be installed in "tools/${PORT}" directory
if(QMSETUP_VCPKG_TOOLS_HINT)
set(_tools_dir tools/${QMSETUP_INSTALL_NAME})
else()
set(_tools_dir ${CMAKE_INSTALL_BINDIR})
endif()
install(TARGETS ${PROJECT_NAME}
EXPORT ${QMSETUP_INSTALL_NAME}Targets
RUNTIME DESTINATION "${_tools_dir}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
# Check patchelf
if (NOT WIN32 AND NOT APPLE)
execute_process(
COMMAND patchelf --version
OUTPUT_VARIABLE _patchelf_version_output
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT "${_patchelf_version_output}" MATCHES "patchelf")
message(WARNING "Patchelf not found, the deploy feature won't work.")
else()
string(REGEX REPLACE "patchelf (.+)" "\\1" _patchelf_version ${_patchelf_version_output})
message(STATUS "Found patchelf, version ${_patchelf_version}")
endif()
endif()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,210 @@
#include "sha-256.h"
#include <cstring>
#include <string>
static constexpr const auto CHUNK_SIZE = 64;
static constexpr const auto TOTAL_LEN_LEN = 8;
/*
* ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible.
*/
/*
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
* When useful for clarification, portions of the pseudo-code are reproduced here too.
*/
/*
* Initialize array of round constants:
* (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
*/
static constexpr const uint32_t k[] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
struct buffer_state {
const uint8_t * p;
size_t len;
size_t total_len;
int single_one_delivered; /* bool */
int total_len_delivered; /* bool */
};
static inline constexpr uint32_t right_rot(uint32_t value, unsigned int count)
{
/*
* Defined behaviour in standard C for all count where 0 < count < 32,
* which is what we need here.
*/
return value >> count | value << (32 - count);
}
static void init_buf_state(struct buffer_state * state, const void * input, size_t len)
{
state->p = static_cast<const uint8_t*>(input);
state->len = len;
state->total_len = len;
state->single_one_delivered = 0;
state->total_len_delivered = 0;
}
/* Return value: bool */
static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state)
{
size_t space_in_chunk;
if (state->total_len_delivered) {
return 0;
}
if (state->len >= CHUNK_SIZE) {
std::memcpy(chunk, state->p, CHUNK_SIZE);
state->p += CHUNK_SIZE;
state->len -= CHUNK_SIZE;
return 1;
}
std::memcpy(chunk, state->p, state->len);
chunk += state->len;
space_in_chunk = CHUNK_SIZE - state->len;
state->p += state->len;
state->len = 0;
/* If we are here, space_in_chunk is one at minimum. */
if (!state->single_one_delivered) {
*chunk++ = 0x80;
space_in_chunk -= 1;
state->single_one_delivered = 1;
}
/*
* Now:
* - either there is enough space left for the total length, and we can conclude,
* - or there is too little space left, and we have to pad the rest of this chunk with zeroes.
* In the latter case, we will conclude at the next invokation of this function.
*/
if (space_in_chunk >= TOTAL_LEN_LEN) {
const size_t left = space_in_chunk - TOTAL_LEN_LEN;
size_t len = state->total_len;
int i;
std::memset(chunk, 0x00, left);
chunk += left;
/* Storing of len * 8 as a big endian 64-bit without overflow. */
chunk[7] = (uint8_t) (len << 3);
len >>= 5;
for (i = 6; i >= 0; i--) {
chunk[i] = (uint8_t) len;
len >>= 8;
}
state->total_len_delivered = 1;
} else {
std::memset(chunk, 0x00, space_in_chunk);
}
return 1;
}
/*
* Limitations:
* - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
* for large data sizes.
* - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
* for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
* In particular, the len parameter is a number of bytes.
*/
void calc_sha_256(uint8_t hash[32], const void * input, size_t len)
{
/*
* Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.
* Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
* Note 3: The compression function uses 8 working variables, a through h
* Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
* and when parsing message block data from bytes to words, for example,
* the first word of the input message "abc" after padding is 0x61626380
*/
/*
* Initialize hash values:
* (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
*/
uint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
int i, j;
/* 512-bit chunks is what we will operate on. */
uint8_t chunk[64];
struct buffer_state state;
init_buf_state(&state, input, len);
while (calc_chunk(chunk, &state)) {
uint32_t ah[8];
/*
* create a 64-entry message schedule array w[0..63] of 32-bit words
* (The initial values in w[0..63] don't matter, so many implementations zero them here)
* copy chunk into first 16 words w[0..15] of the message schedule array
*/
uint32_t w[64];
const uint8_t *p = chunk;
std::memset(w, 0x00, sizeof w);
for (i = 0; i < 16; i++) {
w[i] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 |
(uint32_t) p[2] << 8 | (uint32_t) p[3];
p += 4;
}
/* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */
for (i = 16; i < 64; i++) {
const uint32_t s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3);
const uint32_t s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
/* Initialize working variables to current hash value: */
for (i = 0; i < 8; i++)
ah[i] = h[i];
/* Compression function main loop: */
for (i = 0; i < 64; i++) {
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
const uint32_t temp1 = ah[7] + s1 + ch + k[i] + w[i];
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
const uint32_t temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
/* Add the compressed chunk to the current hash value: */
for (i = 0; i < 8; i++)
h[i] += ah[i];
}
/* Produce the final hash value (big-endian): */
for (i = 0, j = 0; i < 8; i++)
{
hash[j++] = (uint8_t) (h[i] >> 24);
hash[j++] = (uint8_t) (h[i] >> 16);
hash[j++] = (uint8_t) (h[i] >> 8);
hash[j++] = (uint8_t) h[i];
}
}
@@ -0,0 +1,19 @@
#ifndef SHA_256_H
#define SHA_256_H
// https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/src/MISC/sha2
#include <cstddef>
#include <cstdint>
#ifdef __cplusplus
extern "C" {
#endif
void calc_sha_256(uint8_t hash[32], const void *input, size_t len);
#ifdef __cplusplus
}
#endif
#endif // SHA_256_H
@@ -0,0 +1,31 @@
#include "utils.h"
namespace fs = std::filesystem;
#ifdef _WIN32
# define _TSTR(X) L##X
# define tstrcmp wcscmp
#else
# define _TSTR(X) X
# define tstrcmp strcmp
#endif
namespace Utils {
std::filesystem::path cleanPath(const std::filesystem::path &path) {
fs::path result;
for (const auto &part : path) {
if (part == _TSTR("..")) {
if (!result.empty() && result.filename() != _TSTR("..")) {
result = result.parent_path();
} else {
result /= part;
}
} else if (part != _TSTR(".")) {
result /= part;
}
}
return result;
}
}
+158
View File
@@ -0,0 +1,158 @@
#ifndef UTILS_H
#define UTILS_H
#include <string>
#include <vector>
#include <filesystem>
#include <chrono>
#include <algorithm>
#include <cctype>
#include <vector>
namespace Utils {
// Filesystem Utils
struct FileTime {
std::chrono::system_clock::time_point accessTime;
std::chrono::system_clock::time_point modifyTime;
std::chrono::system_clock::time_point statusChangeTime; // Creation time on Windows
};
FileTime fileTime(const std::filesystem::path &path);
void setFileTime(const std::filesystem::path &path, const FileTime &times);
inline void syncFileTime(const std::filesystem::path &dest, const std::filesystem::path &src) {
setFileTime(dest, fileTime(src));
}
std::vector<std::filesystem::path> getPathsFromEnv();
std::filesystem::path cleanPath(const std::filesystem::path &path);
// String Utils
template <class T>
std::vector<std::basic_string<T>> split(const std::basic_string<T> &s,
const std::basic_string<T> &delimiter) {
std::vector<std::basic_string<T>> tokens;
typename std::basic_string<T>::size_type start = 0;
typename std::basic_string<T>::size_type end = s.find(delimiter);
while (end != std::basic_string<T>::npos) {
tokens.push_back(s.substr(start, end - start));
start = end + delimiter.size();
end = s.find(delimiter, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
template <class T>
std::basic_string<T> trim(const std::basic_string<T> &str) {
auto start = str.begin();
while (start != str.end() && std::isspace(*start)) {
start++;
}
auto end = str.end();
do {
end--;
} while (std::distance(start, end) > 0 && std::isspace(*end));
return {start, end + 1};
}
template <class T>
void replaceString(std::basic_string<T> &s, const std::basic_string<T> &pattern,
const std::basic_string<T> &text) {
size_t idx = 0;
while ((idx = s.find(pattern, idx)) != std::basic_string<T>::npos) {
s.replace(idx, pattern.size(), text);
idx += text.size();
}
};
template <class T>
std::basic_string<T> join(const std::vector<std::basic_string<T>> &v,
const std::basic_string<T> &delimiter) {
if (v.empty())
return {};
std::basic_string<T> res;
for (int i = 0; i < v.size() - 1; ++i) {
res.append(v[i]);
res.append(delimiter);
}
res.append(v.back());
return res;
}
template <class T>
std::basic_string<T> toLower(std::basic_string<T> s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
}
template <class T>
std::basic_string<T> toUpper(std::basic_string<T> s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
inline bool starts_with(const std::string_view &s, const std::string_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
inline bool starts_with(const std::wstring_view &s, const std::wstring_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
template <class Container, class T>
inline bool contains(const Container &container, const T &key) {
#if __cplusplus >= 202002L
return container.contains(key);
#else
return container.count(key) != 0;
#endif
}
// OS Utils
#ifdef _WIN32
std::wstring executeCommand(const std::wstring &command, const std::vector<std::wstring> &args);
std::vector<std::wstring>
resolveWinBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed);
#else
std::string executeCommand(const std::string &command, const std::vector<std::string> &args);
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths);
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed = nullptr);
#endif
#ifdef __APPLE__
std::vector<std::string> getMacAbsoluteDependencies(const std::string &file);
void replaceMacFileDependencies(
const std::string &file, const std::vector<std::pair<std::string, std::string>> &depPairs);
#elif defined(__linux__)
std::string getInterpreter(const std::string &file);
bool setFileInterpreter(const std::string &file, const std::string &interpreter);
#endif
}
#endif // UTILS_H
@@ -0,0 +1,500 @@
#include "utils.h"
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utime.h>
#include <cstring>
#include <filesystem>
#include <regex>
#include <set>
#include <sstream>
namespace fs = std::filesystem;
namespace Utils {
std::string executeCommand(const std::string &command, const std::vector<std::string> &args) {
// printf("Executing command: %s", command.data());
// for (const auto &arg : args) {
// printf(" %s", arg.data());
// }
// printf("\n");
static const auto &dupStr = [](char *&dest, const char *src, size_t size) {
dest = new char[size + 1];
memcpy(dest, src, size);
dest[size] = '\0';
};
enum PipeNum {
PN_Read,
PN_Write,
};
// Create pipe
int pipefd[2];
if (pipe(pipefd) == -1) {
throw std::runtime_error("failed to call \"pipe\"");
}
pid_t pid = fork();
if (pid < 0) {
// Close pipes right away
close(pipefd[PN_Read]);
close(pipefd[PN_Write]);
throw std::runtime_error("failed to call \"fork\"");
}
if (pid == 0) {
// --------
// Child process
// --------
// Close read pipe
close(pipefd[PN_Read]);
// Redirect "stdout" and "stderr" to write pipe
dup2(pipefd[PN_Write], STDOUT_FILENO);
dup2(pipefd[PN_Write], STDERR_FILENO);
// Prepare arguments
auto argv = new char *[args.size() + 2]; // +2 for command and nullptr
dupStr(argv[0], command.data(), command.size());
for (size_t i = 0; i < args.size(); ++i) {
dupStr(argv[i + 1], args[i].data(), args[i].size());
}
argv[args.size() + 1] = nullptr;
// Call "exec"
execvp(argv[0], argv);
// If the control flow reaches here, there must be a mistake
// Clean allocated memory
for (size_t i = 0; i < args.size() + 2; ++i) {
delete[] argv[i];
}
delete[] argv;
// Fail
printf("exec \"%s\" failed: %s\n", command.data(), strerror(errno));
// No need to close write pipe, let the System handle everything
// Simply exit
exit(EXIT_FAILURE);
}
// --------
// Parent process
// --------
// Close write pipe
close(pipefd[PN_Write]);
// Read child process output
char buffer[256];
std::string output;
ssize_t bytesRead;
while ((bytesRead = read(pipefd[0], buffer, sizeof(buffer) - 1)) > 0) {
buffer[bytesRead] = '\0';
output += buffer;
}
// Close read pipe
close(pipefd[PN_Read]);
// Wait for child process to terminate
int status;
waitpid(pid, &status, 0);
// Check exit status
if (WIFEXITED(status)) {
auto exitCode = WEXITSTATUS(status);
if (exitCode == 0)
return output;
// Throw error
throw std::runtime_error(trim(output));
}
if (WIFSIGNALED(status)) {
auto sig = WTERMSIG(status);
throw std::runtime_error("command \"" + command + "\" was terminated by signal " +
std::to_string(sig));
}
throw std::runtime_error("command \"" + command + "\" terminated abnormally with status " +
std::to_string(status));
}
FileTime fileTime(const fs::path &path) {
struct stat sb;
if (stat(path.c_str(), &sb) == -1) {
throw std::runtime_error("failed to get file time: \"" + path.string() + "\"");
}
FileTime times;
times.accessTime = std::chrono::system_clock::from_time_t(sb.st_atime);
times.modifyTime = std::chrono::system_clock::from_time_t(sb.st_mtime);
times.statusChangeTime = std::chrono::system_clock::from_time_t(sb.st_ctime);
return times;
}
void setFileTime(const fs::path &path, const FileTime &times) {
struct utimbuf new_times;
new_times.actime = std::chrono::system_clock::to_time_t(times.accessTime);
new_times.modtime = std::chrono::system_clock::to_time_t(times.modifyTime);
if (utime(path.c_str(), &new_times) != 0) {
throw std::runtime_error("failed to set file time: \"" + path.string() + "\"");
}
}
std::vector<fs::path> getPathsFromEnv() {
const char *pathEnv = std::getenv("PATH");
if (pathEnv == nullptr) {
return {};
}
std::string pathStr = pathEnv;
std::stringstream ss(pathStr);
std::string item;
std::vector<fs::path> paths;
while (std::getline(ss, item, ':')) {
if (!item.empty()) {
paths.push_back(fs::path(item));
}
}
return paths;
}
#ifdef __APPLE__
// Mac
// Use `otool` and `install_name_tool`
static std::vector<std::string> readMacBinaryRPaths(const std::string &path) {
std::vector<std::string> rpaths;
std::string output;
try {
output = executeCommand("otool", {"-l", path});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get RPATHs: " + std::string(e.what()));
}
static const std::regex rpathRegex(R"(\s*path\s+(.*)\s+\(offset.*)");
std::istringstream iss(output);
std::string line;
std::smatch match;
while (std::getline(iss, line)) {
if (line.find("cmd LC_RPATH") != std::string::npos) {
// skip 2 lines
std::getline(iss, line);
std::getline(iss, line);
if (std::regex_match(line, match, rpathRegex) && match.size() >= 2) {
rpaths.emplace_back(match[1].str());
}
}
}
return rpaths;
}
static std::vector<std::string> readMacBinaryDependencies(const std::string &path) {
std::vector<std::string> dependencies;
std::string output;
// Get dependencies
try {
output = executeCommand("otool", {"-L", path});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get dependencies: " + std::string(e.what()));
}
static const std::regex depRegex(
R"(^\t(.+) \(compatibility version (\d+\.\d+\.\d+), current version (\d+\.\d+\.\d+)(, weak)?\)$)");
std::istringstream iss(output);
std::string line;
std::smatch match;
// skip first line
std::getline(iss, line);
const std::string &loaderPath = path;
while (std::getline(iss, line)) {
if (std::regex_search(line, match, depRegex) && match.size() >= 2) {
std::string dep = match[1].str();
dependencies.emplace_back(dep);
}
}
return dependencies;
}
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
auto rpaths = readMacBinaryRPaths(path);
for (const auto &item : searchingPaths) {
rpaths.push_back(item);
}
auto dependencies = readMacBinaryDependencies(path);
const std::string &loaderPath = fs::canonical(path).parent_path();
std::vector<std::string> res;
for (auto dep : std::as_const(dependencies)) {
// Replace @executable_path and @loader_path
replaceString(dep, std::string("@executable_path"), loaderPath);
replaceString(dep, std::string("@loader_path"), loaderPath);
// Find dependency
std::string target = dep;
if (dep.find("@rpath") != std::string::npos) {
for (auto rpath : rpaths) {
// Replace again
replaceString(rpath, std::string("@executable_path"), loaderPath);
replaceString(rpath, std::string("@loader_path"), loaderPath);
std::string fullPath = dep;
replaceString(fullPath, std::string("@rpath"), rpath);
if (fs::exists(fullPath)) {
target = fullPath;
break;
}
}
}
target = cleanPath(target);
if (fs::exists(target)) {
if (fs::canonical(target).filename() == fs::canonical(path).filename())
continue;
res.push_back(target);
} else if (unparsed) {
unparsed->push_back(target);
}
}
return res;
}
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths) {
// Remove rpaths
do {
auto rpaths = readMacBinaryRPaths(file);
if (rpaths.empty())
break;
std::vector<std::string> args;
args.reserve(rpaths.size() * 2 + 1);
for (const auto &rpath : std::as_const(rpaths)) {
args.push_back("-delete_rpath");
args.push_back(rpath);
}
args.push_back(file);
try {
std::ignore = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to remove rpaths: " + std::string(e.what()));
}
} while (false);
// Add rpaths
if (!paths.empty()) {
std::set<std::string> visited;
std::vector<std::string> args;
args.reserve(paths.size() * 2 + 1);
for (const auto &rpath : std::as_const(paths)) {
if (Utils::contains(visited, rpath))
continue;
visited.insert(rpath);
args.push_back("-add_rpath");
args.push_back(rpath);
}
args.push_back(file);
try {
std::ignore = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to add rpaths: " + std::string(e.what()));
}
}
// Remove code signature if it exists
try {
std::ignore = executeCommand("codesign", {"--remove-signature", file});
std::ignore = executeCommand("codesign", {"-s", "-", file});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to resign: " + std::string(e.what()));
}
}
std::vector<std::string> getMacAbsoluteDependencies(const std::string &file) {
auto deps = readMacBinaryDependencies(file);
std::vector<std::string> res;
for (const auto &dep : std::as_const(deps)) {
if (fs::path(dep).is_absolute()) {
res.push_back(dep);
}
}
return res;
}
void replaceMacFileDependencies(
const std::string &file, const std::vector<std::pair<std::string, std::string>> &depPairs) {
std::string output;
std::vector<std::string> args;
args.reserve(depPairs.size() * 3 + 1);
std::string id;
for (const auto &pair : depPairs) {
if (fs::exists(pair.first) &&
fs::canonical(pair.first).filename() == fs::canonical(file).filename()) {
id = pair.second;
continue;
}
args.push_back("-change");
args.push_back(pair.first);
args.push_back(pair.second);
}
if (!id.empty()) {
args.push_back("-id");
args.push_back(id);
}
args.push_back(file);
try {
output = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to replace dependency: " + std::string(e.what()));
}
}
#else
// Linux
// Use `ldd` and `patchelf`
static std::vector<std::string>
readLddOutput(const std::string &fileName,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
std::string output;
try {
output = executeCommand("ldd", {fileName});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get dependencies: " + std::string(e.what()));
}
std::istringstream iss(output);
std::string line;
static const std::regex regexp("^\\s*.+ => (.+) \\(.*");
static const std::regex regexp2("^\\s*(.+) => not found");
std::vector<std::string> dependencies;
while (std::getline(iss, line)) {
std::smatch match;
if (std::regex_match(line, match, regexp) && match.size() >= 2) {
dependencies.push_back(cleanPath(match[1].str()));
} else if (std::regex_match(line, match, regexp2) && match.size() >= 2) {
// Search in search paths
fs::path target;
for (const auto &item : searchingPaths) {
auto fullPath = item / match[1].str();
if (fs::exists(fullPath)) {
target = fullPath;
break;
}
}
if (!target.empty()) {
dependencies.push_back(target);
} else {
unparsed->push_back(cleanPath(match[1].str()));
}
}
}
return dependencies;
}
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
return readLddOutput(path, searchingPaths, unparsed);
}
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths) {
if (paths.empty()) {
try {
std::ignore = executeCommand("patchelf", {
"--remove-rpath",
file,
});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to remove rpaths: " + std::string(e.what()));
}
return;
}
try {
std::ignore = executeCommand("patchelf", {
"--set-rpath",
join(paths, std::string(":")),
file,
});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to replace rpaths: " + std::string(e.what()));
}
}
#endif
#ifdef __linux__
static inline bool errorIsInterpNotFound(const std::string &what) {
return what.find("cannot find section '.interp'") != std::string::npos;
};
std::string getInterpreter(const std::string &file) {
std::string output;
try {
output = executeCommand("patchelf", {
"--print-interpreter",
file,
});
} catch (const std::exception &e) {
if (errorIsInterpNotFound(e.what()))
return {};
throw std::runtime_error("Failed to get interpreter: " + std::string(e.what()));
}
output = trim(output);
replaceString(output, std::string("$ORIGIN"), fs::canonical(file).parent_path().string());
return output;
}
bool setFileInterpreter(const std::string &file, const std::string &interpreter) {
try {
std::ignore = executeCommand("patchelf", {
"--set-interpreter",
interpreter,
file,
});
} catch (const std::exception &e) {
if (errorIsInterpNotFound(e.what()))
return false;
throw std::runtime_error("Failed to set interpreter: " + std::string(e.what()));
}
return true;
}
#endif
}
@@ -0,0 +1,500 @@
#include "utils.h"
#include <shlwapi.h>
#include <delayimp.h>
#include <windows.h>
#include <algorithm>
#include <sstream>
#include <filesystem>
#include <stdexcept>
#include <utility>
#include <syscmdline/system.h>
namespace SCL = SysCmdLine;
namespace fs = std::filesystem;
namespace Utils {
static constexpr const DWORD g_EnglishLangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
static std::wstring winErrorMessage(uint32_t error, bool nativeLanguage = true) {
std::wstring rc;
wchar_t *lpMsgBuf;
DWORD len = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, nativeLanguage ? 0 : g_EnglishLangId,
reinterpret_cast<LPWSTR>(&lpMsgBuf), 0, NULL);
if (len) {
// Remove tail line breaks
if (lpMsgBuf[len - 1] == L'\n') {
lpMsgBuf[len - 1] = L'\0';
if (len > 2 && lpMsgBuf[len - 2] == L'\r') {
lpMsgBuf[len - 2] = L'\0';
}
}
rc = std::wstring(lpMsgBuf, int(len));
::LocalFree(lpMsgBuf);
} else {
rc += L"unknown error";
}
return rc;
}
static std::wstring quoteAndEscapeArg(const std::wstring &arg) {
if (arg.find_first_of(L" \t\"") == std::wstring::npos) {
// No need to quote or escape
return arg;
}
std::wstring escapedArg = L"\"";
for (auto ch : arg) {
if (ch == L'"') {
// Escape double quotes
escapedArg += L"\"\"";
} else {
escapedArg += ch;
}
}
escapedArg += L"\"";
return escapedArg;
}
std::wstring executeCommand(const std::wstring &command,
const std::vector<std::wstring> &args) {
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe, hWritePipe;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
DWORD read;
wchar_t buffer[4096]; // For captured output
// Initialize security attributes to allow pipe handles to be inherited.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!::CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
throw std::runtime_error("failed to call \"CreatePipe\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!::SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0)) {
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
throw std::runtime_error("failed to call \"SetHandleInformation\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Set up members of the STARTUPINFO structure.
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
// Construct command line
std::wstringstream cmdLineStream;
cmdLineStream << quoteAndEscapeArg(command) << L" ";
for (const auto &arg : args) {
cmdLineStream << quoteAndEscapeArg(arg) << L" ";
}
std::wstring cmdLine = cmdLineStream.str();
// Start the child process.
if (!::CreateProcessW(NULL, cmdLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
throw std::runtime_error("failed to call \"CreateProcess\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Close the write end of the pipe before reading from the read end of the pipe.
::CloseHandle(hWritePipe);
// Read output from the child process's pipe for STDOUT
// and write to the parent process's STDOUT.
std::wstring output;
while (true) {
if (!::ReadFile(hReadPipe, buffer, sizeof(buffer) - sizeof(wchar_t), &read, NULL) ||
read == 0)
break;
buffer[read / sizeof(wchar_t)] = L'\0';
output += buffer;
}
// Wait until child process exits.
::WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitCode;
::GetExitCodeProcess(pi.hProcess, &exitCode);
// Close process and thread handles and the read end of the pipe.
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
::CloseHandle(hReadPipe);
if (exitCode == 0)
return output;
throw std::runtime_error(SCL::wideToUtf8(output));
}
// Helper functions to convert between FILETIME and std::chrono::system_clock::time_point
static std::chrono::system_clock::time_point filetime_to_timepoint(const FILETIME &ft) {
// Windows file time starts from January 1, 1601
// std::chrono::system_clock starts from January 1, 1970
static constexpr const long long WIN_EPOCH =
116444736000000000LL; // in hundreds of nanoseconds
long long duration = (static_cast<long long>(ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
duration -= WIN_EPOCH; // convert to Unix epoch
return std::chrono::system_clock::from_time_t(duration / 10000000LL);
}
static FILETIME timepoint_to_filetime(const std::chrono::system_clock::time_point &tp) {
FILETIME ft;
static constexpr const long long WIN_EPOCH =
116444736000000000LL; // in hundreds of nanoseconds
long long duration =
std::chrono::duration_cast<std::chrono::microseconds>(tp.time_since_epoch()).count();
duration = duration * 10 + WIN_EPOCH;
ft.dwLowDateTime = static_cast<DWORD>(duration & 0xFFFFFFFF);
ft.dwHighDateTime = static_cast<DWORD>((duration >> 32) & 0xFFFFFFFF);
return ft;
}
FileTime fileTime(const fs::path &path) {
HANDLE hFile = ::CreateFileW(path.wstring().data(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("invalid path: \"" + SCL::wideToUtf8(path.wstring()) + "\"");
}
FILETIME creationTime, lastAccessTime, lastWriteTime;
if (!::GetFileTime(hFile, &creationTime, &lastAccessTime, &lastWriteTime)) {
::CloseHandle(hFile);
throw std::runtime_error("failed to get file time: \"" +
SCL::wideToUtf8(path.wstring()) + "\"");
}
::CloseHandle(hFile);
FileTime times;
// ... (convert FILETIMEs to std::chrono::system_clock::time_point and store in times)
times.accessTime = filetime_to_timepoint(lastAccessTime);
times.modifyTime = filetime_to_timepoint(lastWriteTime);
times.statusChangeTime = filetime_to_timepoint(creationTime);
return times;
}
void setFileTime(const fs::path &path, const FileTime &times) {
HANDLE hFile = ::CreateFileW(path.wstring().data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("invalid path: \"" + SCL::wideToUtf8(path.wstring()) + "\"");
}
FILETIME creationTime, lastAccessTime, lastWriteTime;
lastAccessTime = timepoint_to_filetime(times.accessTime);
lastWriteTime = timepoint_to_filetime(times.modifyTime);
creationTime = timepoint_to_filetime(times.statusChangeTime);
if (!::SetFileTime(hFile, &creationTime, &lastAccessTime, &lastWriteTime)) {
::CloseHandle(hFile);
throw std::runtime_error("failed to set file time: \"" +
SCL::wideToUtf8(path.wstring()) + "\"");
}
::CloseHandle(hFile);
}
std::vector<std::filesystem::path> getPathsFromEnv() {
DWORD length = ::GetEnvironmentVariableW(L"PATH", nullptr, 0);
if (length == 0) {
return {};
}
auto buf = new wchar_t[length];
::GetEnvironmentVariableW(L"PATH", buf, length);
std::wstring pathStr = buf;
delete[] buf;
std::wstringstream wss(pathStr);
std::wstring item;
std::vector<fs::path> paths;
while (std::getline(wss, item, L';')) {
if (!item.empty()) {
paths.push_back(item);
}
}
return paths;
}
// ================================================================================
// Modified from windeployqt 5.15.2(Copyright Qt company)
// ================================================================================
namespace WindowsDeployQt {
static inline std::string stringFromRvaPtr(const void *rvaPtr) {
return static_cast<const char *>(rvaPtr);
}
// Helper for reading out PE executable files: Find a section header for an RVA
// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
template <class ImageNtHeader>
static const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva,
const ImageNtHeader *nTHeader) {
const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
const IMAGE_SECTION_HEADER *sectionEnd =
section + nTHeader->FileHeader.NumberOfSections;
for (; section < sectionEnd; ++section)
if (rva >= section->VirtualAddress &&
rva < (section->VirtualAddress + section->Misc.VirtualSize))
return section;
return 0;
}
// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64,
// IMAGE_NT_HEADERS32).
template <class ImageNtHeader>
inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader,
const void *imageBase) {
const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
if (!sectionHdr)
return 0;
const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
return static_cast<const char *>(imageBase) + rva - delta;
}
// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64,
// IMAGE_NT_HEADERS32
template <class ImageNtHeader>
static unsigned ntHeaderWordSize(const ImageNtHeader *header) {
// defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
return 32;
if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
return 64;
return 0;
}
// Helper for reading out PE executable files: Retrieve the NT image header of an
// executable via the legacy DOS header.
static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, std::wstring *errorMessage) {
IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
// Check DOS header consistency
if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) ||
dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
*errorMessage = L"DOS header check failed.";
return 0;
}
// Retrieve NT header
char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew;
IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
// check NT header consistency
if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature)) ||
ntHeaders->Signature != IMAGE_NT_SIGNATURE ||
IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
*errorMessage = L"NT header check failed.";
return 0;
}
// Check magic
if (!ntHeaderWordSize(ntHeaders)) {
std::wostringstream ss;
ss << "NT header check failed; magic " << ntHeaders->OptionalHeader.Magic
<< " is invalid.";
*errorMessage = ss.str();
return 0;
}
// Check section headers
IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections *
sizeof(IMAGE_SECTION_HEADER))) {
*errorMessage = L"NT header section header check failed.";
return 0;
}
return ntHeaders;
}
// Helper for reading out PE executable files: Read out import sections from
// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32.
template <class ImageNtHeader>
static std::vector<std::string> readImportSections(const ImageNtHeader *ntHeaders,
const void *base,
std::wstring *errorMessage) {
// Get import directory entry RVA and read out
const DWORD importsStartRVA =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
.VirtualAddress;
if (!importsStartRVA) {
*errorMessage = L"Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.";
return {};
}
const IMAGE_IMPORT_DESCRIPTOR *importDesc =
static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(
rvaToPtr(importsStartRVA, ntHeaders, base));
if (!importDesc) {
*errorMessage = L"Failed to find IMAGE_IMPORT_DESCRIPTOR entry.";
return {};
}
std::vector<std::string> result;
for (; importDesc->Name; ++importDesc)
result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base)));
// Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
// Check on grAttr bit 1 whether this is the format using RVA's > VS 6
if (const DWORD delayedImportsStartRVA =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]
.VirtualAddress) {
const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(
rvaToPtr(delayedImportsStartRVA, ntHeaders, base));
for (; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1);
++delayedImportDesc)
result.push_back(
stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base)));
}
return result;
}
template <class ImageNtHeader>
static void determineDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
bool isMinGW,
std::vector<std::string> *dependentLibrariesIn,
std::wstring *errorMessage) {
std::vector<std::string> dependentLibraries;
if (dependentLibrariesIn)
dependentLibraries = readImportSections(nth, fileMemory, errorMessage);
if (dependentLibrariesIn)
*dependentLibrariesIn = dependentLibraries;
}
// Read a PE executable and determine dependent libraries, word size.
bool readPeExecutable(const std::wstring &peExecutableFileName, std::wstring *errorMessage,
std::vector<std::string> *dependentLibrariesIn, unsigned *wordSizeIn,
bool isMinGW, unsigned short *machineArchIn) {
bool result = false;
HANDLE hFile = NULL;
HANDLE hFileMap = NULL;
void *fileMemory = 0;
if (dependentLibrariesIn)
dependentLibrariesIn->clear();
if (wordSizeIn)
*wordSizeIn = 0;
do {
// Create a memory mapping of the file
hFile = CreateFileW(peExecutableFileName.data(), GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
std::wostringstream ss;
ss << L"Cannot open \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMap == NULL) {
std::wostringstream ss;
ss << L"Cannot create file mapping of \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!fileMemory) {
std::wostringstream ss;
ss << L"Cannot map \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
if (!ntHeaders)
break;
const unsigned wordSize = ntHeaderWordSize(ntHeaders);
if (wordSizeIn)
*wordSizeIn = wordSize;
if (wordSize == 32) {
determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders),
fileMemory, isMinGW, dependentLibrariesIn, errorMessage);
} else {
determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders),
fileMemory, isMinGW, dependentLibrariesIn, errorMessage);
}
if (machineArchIn)
*machineArchIn = ntHeaders->FileHeader.Machine;
result = true;
} while (false);
if (fileMemory)
UnmapViewOfFile(fileMemory);
if (hFileMap != NULL)
CloseHandle(hFileMap);
if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return result;
}
}
std::vector<std::wstring>
resolveWinBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
std::wstring errorMessage;
std::vector<std::string> dependentLibrariesIn;
unsigned wordSizeIn;
bool isMinGW = false;
unsigned short machineArchIn;
if (!WindowsDeployQt::readPeExecutable(path, &errorMessage, &dependentLibrariesIn,
&wordSizeIn, isMinGW, &machineArchIn)) {
throw std::runtime_error(SCL::wideToUtf8(errorMessage));
}
// Search
std::vector<std::wstring> result;
for (const auto &item : std::as_const(dependentLibrariesIn)) {
fs::path fullPath;
for (const auto &dir : std::as_const(searchingPaths)) {
fs::path targetPath = dir / item;
if (fs::exists(targetPath)) {
fullPath = targetPath;
break;
}
}
if (!fullPath.empty()) {
result.push_back(fullPath);
continue;
}
if (unparsed) {
unparsed->push_back(item);
}
}
return result;
}
}
@@ -0,0 +1,13 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/qmsetupTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/QMSetupAPI.cmake")
if ("All" IN_LIST qmsetup_FIND_COMPONENTS)
qm_import_all()
else()
qm_import(${qmsetup_FIND_COMPONENTS})
endif()
@@ -0,0 +1,130 @@
# References:
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format
BasedOnStyle: LLVM
Standard: c++17
# 指针和引用的对齐方式。
# 可能的值有:
# PAS_Left (在配置中: Left) 指针左对齐。
# PAS_Right (在配置中: Right) 指针右对齐。
# PAS_Middle (在配置中: Middle) 指针中间对齐。
PointerAlignment: Right
# public/protected/private 等访问修饰符偏移量
AccessModifierOffset: -4
# 缩进长度
IndentWidth: 4
# 连续空行的最大数
MaxEmptyLinesToKeep: 999
# 在OC中的@property后面添加一个空格。例如:使用“@property (readonly)”而不是“@property(readonly)”
ObjCSpaceAfterProperty: true
# OC块中所拍的字符数
ObjCBlockIndentWidth: 4
# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。
# 可能的值有:
# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。
# SFS_Empty (在配置中: Empty) 仅合并空的函数。
# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”.
# SFS_All (在配置中: All) 合并所有的方法适应单行.
AllowShortFunctionsOnASingleLine: None
# 如果为真(true), 语句“if (a) return;” 能被放到单行。
AllowShortIfStatementsOnASingleLine: false
# 如果为真(true), 对齐注释。
AlignTrailingComments: true
# 如果为真,对齐连续的宏定义
AlignConsecutiveMacros: true
# 如果为真(true),将会在“[”之后和“]”之前插入空格。
SpacesInSquareBrackets: false
# 如果为真(true), 将会在“(”之后和“)”之前插入空格。
SpacesInParentheses : false
# 如果为真(true), 校准连续的声明。
# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式:
# int aaaa = 12;
# float b = 23;
# std::string ccc = 23;
AlignConsecutiveDeclarations: false
# 如果为真(true),连续调整多行
# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式:
# int aaaa = 12;
# int b = 23;
# int ccc = 23;
AlignConsecutiveAssignments: false
# 如果为假(false),移除分配操作符(=)前空格。
SpaceBeforeAssignmentOperators: true
# 如果为真(true), 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。
SpacesInContainerLiterals: false
# 缩进case标签
IndentCaseLabels: true
# 如果表达式中包含函数调用,并且函数调用因为表达式太长被放到了下一行,是否缩进
IndentWrappedFunctionNames: true
# 如果为真(true), 保持块的起始空行。
# true: false:
# if (foo) { vs. if (foo) {
# bar();
# bar(); }
# }
KeepEmptyLinesAtTheStartOfBlocks: true
# 允许所有参数都被放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 使用C风格强制类型转换后,是否在中间添加一个空格
SpaceAfterCStyleCast: true
# 在模板定义后换行
AlwaysBreakTemplateDeclarations: Yes
# Tab长度
TabWidth: 4
# 是否使用Tab
UseTab: Never
# 在括号后对齐参数
# someLongFunction(argument1,
# argument2);
AlignAfterOpenBracket: Align
# 名字空间内部缩进
NamespaceIndentation: All
# 一行最长列数
ColumnLimit: 100
# 按层次缩进宏定义
IndentPPDirectives: AfterHash
# 预处理语句缩进为 2
PPIndentWidth: 2
# 数组元素对齐
AlignArrayOfStructures: Left
# 不对头文件排序
SortIncludes: Never
FixNamespaceComments: false
StatementMacros: ['__qas_attr__', '__qas_exclude__', '__qas_include__']
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]
@@ -0,0 +1,98 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.log
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
# *.res
# *.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
__pycache__
*.pyc
# Binaries
# --------
*.dll
*.exe
.DS_Store
*/.DS_Store
build-src*
build/
cmake-build*
*.user
*.lnk
_workingDir*
.vscode
.idea
.cache
cache
.vs
out/
CMakeSettings.json
# /vcpkg
/data
/*.natvis
*.sublime-*
setup-vcpkg.json
setup-vcpkg-temp*
@@ -0,0 +1,165 @@
cmake_minimum_required(VERSION 3.17)
project(syscmdline VERSION 1.0.0.0 LANGUAGES C CXX)
# ----------------------------------
# Build Options
# ----------------------------------
option(SYSCMDLINE_BUILD_STATIC "Build static library" ON)
option(SYSCMDLINE_BUILD_EXAMPLES "Build examples" OFF)
option(SYSCMDLINE_BUILD_TESTS "Build test cases" OFF)
option(SYSCMDLINE_FORCE_VALIDITY_CHECK "Force to enable validity check" OFF)
option(SYSCMDLINE_INSTALL "Install library" ON)
# ----------------------------------
# CMake Settings
# ----------------------------------
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_BUILD_SHARE_DIR)
set(CMAKE_BUILD_SHARE_DIR ${CMAKE_BINARY_DIR}/share)
endif()
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX "d")
endif()
endif()
if(SYSCMDLINE_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
endif()
# ----------------------------------
# Project Variables
# ----------------------------------
set(SYSCMDLINE_VERSION ${PROJECT_VERSION})
set(SYSCMDLINE_INSTALL_NAME ${PROJECT_NAME})
# ----------------------------------
# Main Project
# ----------------------------------
if(TRUE)
# Add library
if(SYSCMDLINE_BUILD_STATIC)
add_library(${PROJECT_NAME} STATIC)
target_compile_definitions(${PROJECT_NAME} PUBLIC SYSCMDLINE_STATIC)
else()
add_library(${PROJECT_NAME} SHARED)
endif()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Add sources
file(GLOB_RECURSE _src include/syscmdline/*.h src/*.h src/*.cpp)
target_sources(${PROJECT_NAME} PRIVATE ${_src})
# Add features
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 cxx_std_17)
# Add options
target_compile_definitions(${PROJECT_NAME} PRIVATE SYSCMDLINE_LIBRARY)
if(SYSCMDLINE_FORCE_VALIDITY_CHECK)
target_compile_definitions(${PROJECT_NAME} PRIVATE SYSCMDLINE_ENABLE_VALIDITY_CHECK)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE
"$<$<CONFIG:Debug>:SYSCMDLINE_ENABLE_VALIDITY_CHECK>"
)
endif()
# Include directories
target_include_directories(${PROJECT_NAME} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)
target_include_directories(${PROJECT_NAME} PRIVATE include/syscmdline)
# Add platform specific
if(WIN32)
set(RC_DESCRIPTION "Another C++ Command Line Parser")
set(RC_COPYRIGHT "Copyright 2023 SineStriker")
include("cmake/winrc.cmake")
endif()
# if(MSVC)
# if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
# target_compile_options(${PROJECT_NAME} PRIVATE /GL /Os)
# target_link_options(${PROJECT_NAME} PRIVATE /OPT:REF /OPT:ICF /OPT:LBR)
# endif()
# endif()
if(SYSCMDLINE_INSTALL)
target_include_directories(${PROJECT_NAME} PUBLIC
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
install(TARGETS ${PROJECT_NAME}
EXPORT ${SYSCMDLINE_INSTALL_NAME}Targets
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
endif()
endif()
if(SYSCMDLINE_BUILD_TESTS)
add_subdirectory(tests)
endif()
if(SYSCMDLINE_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
if(SYSCMDLINE_INSTALL)
# Add install target
set(_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${SYSCMDLINE_INSTALL_NAME})
# Add version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Add configuration file
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake"
INSTALL_DESTINATION ${_install_dir}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install cmake files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}ConfigVersion.cmake"
DESTINATION ${_install_dir}
)
# Install cmake targets files
install(EXPORT ${SYSCMDLINE_INSTALL_NAME}Targets
FILE "${SYSCMDLINE_INSTALL_NAME}Targets.cmake"
NAMESPACE syscmdline::
DESTINATION ${_install_dir}
)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
endif()
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 SineStriker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,186 @@
# SysCmdLine
Another C++ Command Line Parser.
Inspired by Qt `QCommandLineParser` and C# `System.CommandLine`.
## Introduction
To be in line with the principle of "don't learn anything new if you don't need to", on the one hand, `syscmdline` contains as many common features as possible, on the other hand, it's simple enough to be easy to understand.
Therefore, the project is designed to be configurable, but it's not intended to be as complex as a framework.
## Features
+ Support sub-commands
+ Support case-insensitive parsing
+ Support global options
+ Support mutually exclusive options
+ Support short options and group flags
+ Support help text customization
+ Support localization
+ Simple tips for typo correction
+ Special implementations for Windows
+ Highly configurable
+ Friendly interface
<!-- ## Help Text
The help text is highly configurable, here we show some simple ones.
### Unix Style
```
Description:
Move source(s) to destination directory.
Usage:
mv <src>... <dest> [options]
Arguments:
src Source(s)
dest Destination directory
Options:
-v, --version Show version information
-h, --help Show help information
```
### Dos Style
```
Description:
Deletes one or more files.
Usage:
del files... [/P] [/F] [/S] [/Q] [/?]
Arguments:
files Specifies a list of one or more files or directories.
Options:
/P Prompts for confirmation before deleting the specified file.
/F Forces deletion of read-only files.
/S Deletes specified files from the current directory and all subdirectories.
/Q Specifies quiet mode. You are not prompted for delete confirmation.
/? Show help information.
``` -->
## Simple Example
A simple `mv` command:
```c++
#include <iostream>
#include <syscmdline/parser.h>
namespace SCL = SysCmdLine;
int main(int argc, char *argv[]) {
SCL::Command cmd("mv", "move files to directory");
cmd.addArguments({
SCL::Argument("files", "Source files").multi(),
SCL::Argument("dir", "Destination directory"),
});
cmd.addHelpOption();
cmd.setHandler([](const SCL::ParseResult &result) -> int {
std::cout << "[Sources]" << std::endl;
for (const auto &item : result.values("files")) {
std::cout << item.toString() << std::endl;
}
std::cout << "[Destination]" << std::endl;
std::cout << result.value("dir").toString() << std::endl;
return 0;
});
return SCL::Parser(cmd).invoke(argc, argv);
}
```
Running the code:
```sh
> ./mv --help
Description:
move files to directory
Usage:
mv <files>... <dir> [options]
Arguments:
<files> Source files
<dir> Destination directory
Options:
-h, --help Show help information
```
```sh
> ./mv 1 2
[Sources]
1
[Destination]
2
```
```sh
>./mv 1 2 3
[Sources]
1
2
[Destination]
3
```
## Quick Start
### Concepts
If you are confused about some of the concepts of command line programs, you can learn the following, which will help you use this project.
See [Concepts](docs/concepts.md) to learn more.
### More Examples
See [Examples](docs/examples.md) to learn more.
## CMake Intergration
### Build & Install
```sh
cmake -B build -G Ninja
cmake --build build --target all
cmake --build build --target install
```
### Import
```cmake
find_package(syscmdline REQUIRED)
target_link_libraries(my_project PRIVATE syscmdline::syscmdline)
```
## Notice
<!-- + C++ Standard
+ You need a C++ compiler which supports C++17 to build the library. The interface is compatible with C++11. -->
+ Minimize Size
+ In order to achieve more functionalities, this project contains a large amount of codes so that the binary size may be relatively large compared with other libraries. Therefore, this implementation uses STL templates as little as possible.
+ It's suggested to enable size optimizing option for your compiler when building executables.
+ Validity Check
+ The root command must be valid, otherwise the parsing result is undefined and may even cause crash.
+ Validity checking is enabled if `SYSCMDLINE_ENABLE_VALIDITY_CHECK` is defined, which reduces parsing performance. Therefore, this macro is enabled only in debug mode.
## Thanks
### Other Projects
+ https://github.com/qt/qtbase
+ https://github.com/dotnet/command-line-api
<!-- + https://github.com/tanakh/cmdline
+ https://github.com/p-ranav/argparse
+ https://github.com/CLIUtils/CLI11 -->
### Contributors
+ [wangwenx190](https://github.com/wangwenx190)
+ [JobSecond](https://github.com/JobSecond)
## License
This project is licensed under the MIT License.
@@ -0,0 +1,34 @@
set(_rc_content "#include <windows.h>
#ifndef VS_VERSION_INFO
#define VS_VERSION_INFO 1
#endif
#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
VS_VERSION_INFO VERSIONINFO
FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},${PROJECT_VERSION_TWEAK}
PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},${PROJECT_VERSION_TWEAK}
{
BLOCK \"StringFileInfo\"
{
// U.S. English - Windows, Multilingual
BLOCK \"040904E4\"
{
VALUE \"FileDescription\", STRINGIFY(${RC_DESCRIPTION})
VALUE \"FileVersion\", STRINGIFY(${PROJECT_VERSION})
VALUE \"ProductName\", STRINGIFY(${PROJECT_NAME})
VALUE \"ProductVersion\", STRINGIFY(${PROJECT_VERSION})
VALUE \"LegalCopyright\", STRINGIFY(${RC_COPYRIGHT})
}
}
BLOCK \"VarFileInfo\"
{
VALUE \"Translation\", 0x409, 1252 // 1252 = 0x04E4
}
}")
set(_rc_file ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_res.rc)
file(WRITE ${_rc_file} ${_rc_content})
target_sources(${PROJECT_NAME} PRIVATE ${_rc_file})
@@ -0,0 +1,23 @@
# Concepts
## Command
+ **Command** is the beginning symbol used to determine the behavior of a set of command-line arguments.
+ The commands of a CLI program are usually in the form of a tree structure, and the program itself is the root command. Take `git` as an example, `git` is the root command and `merge`/`commit`/... are the sub-commands.
+ However, most commands doesn't have sub-commands because the root command is sufficient in most scenarios.
+ Command line parameters must start with the root command and list the subcommands in sequence along the path to a certain leaf command. When the target sub-command is listed, all subsequent symbols are regarded as the arguments or options of this sub-command.
## Argument
+ **Argument** is the positional argument, which belongs to a command or an option.
+ The positional arguments are parsed according to the order in which they are specified. Take `mv` as an example, `mv <src> <dest>` shows that it has two positional arguments and the first will be recognized as source and the second as destination.
## Option
+ **Option**, usually a word or letter beginning with the `-` or `/` sign. (`-` is the Unix style and `/` is the Dos style)
+ Options can have none or a fixed number of positional arguments which are recommended to be non-required.
@@ -0,0 +1,251 @@
# Examples
## Basic
+ Acquire Program Arguments
+ Positional Argument
+ Optinal Argument And Restricted Argument
+ Option
+ Multi-argument Option
+ Multi-value Argument
### Acquire Program Arguments
The most common way to get command-line arguments is directly from the arguments of the `main` function.
```c
int main(int argc, char *argv[]);
```
If your host system is Unix, then everything is fine. However, this set of arguments from `main` is likely to cause problems on Windows because they are in ANSI encoding.
On Windows, we must call `GetCommandLineW()` to get the correct UTF-16 encoded arguments.
As a result, `syscmdline` provides a generic way to get command-line arguments that does not depend on the `main` function.
+ On Windows, it acquires arguments by calling `GetCommandLineW()` and converts the result to UTF-8 string list.
+ On Mac, it acquires arguments by calling `_NSGetArgv`.
+ On Linux, it reads the arguments from `/proc/self/cmdline`.
```c++
namespace SysCmdLine {
std::vector<std::string> commandLineArguments();
}
```
Include `system.h` to import this function.
```c++
#include <syscmdline/system.h>
```
If you are developing a program on Windows, we recommend using this function to get command line arguments.
`syscmdline` uses UTF-8 internally to output to the console, error may occur if the ANSI-encoded arguments contain non-ASCII characters.
### Positional Argument
Simple Example of one positional argument:
```c++
#include <iostream>
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
namespace SCL = SysCmdLine;
int main(int /* argc */, char * /* argv */ []) {
SCL::Argument intArg("int", "Integer");
intArg.setDefaultValue(0);
SCL::Command rootCommand("square", "Display the square of the specified integer.");
rootCommand.addArgument(intArg);
rootCommand.addHelpOption();
rootCommand.setHandler([](const SCL::ParseResult &result) {
auto num = result.value("int").toInt();
std::cout << (num * num) << std::endl;
return 0;
});
SCL::Parser parser(rootCommand);
return parser.invoke(SCL::commandLineArguments());
}
```
+ In this case, we call `addArgument` to add one positional argument.
+ The `Argument` instance is default to be required, and we set an implicit defualt value to force the parser to to accept only numeric input.
Help text:
```
Description:
Display the square of a given integer.
Usage:
square <int> [options]
Arguments:
<int> Integer
Options:
-h, --help Show help information
```
Test:
```sh
> ./square 32
1024
```
### Optinal Argument And Restricted Argument
Simple example of one restricted argument and one optional argument:
```c++
int main(int /* argc */, char * /* argv */ []) {
SCL::Argument weekdayArg("weekday", "Weekday");
weekdayArg.setExpectedValues({
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday",
});
SCL::Argument eventArg("event", "Event to do");
eventArg.setDefaultValue("football");
eventArg.setRequired(false);
SCL::Command rootCommand("work", "What to do on which day?");
rootCommand.addArguments({weekdayArg, eventArg});
rootCommand.addHelpOption();
rootCommand.setHandler([](const SCL::ParseResult &result) {
std::cout << result.value("weekday").toString() << std::endl;
std::cout << result.value("event").toString() << std::endl;
return 0;
});
SCL::Parser parser(rootCommand);
parser.setDisplayOptions(SCL::Parser::ShowArgumentDefaultValue |
SCL::Parser::ShowArgumentExpectedValues);
return parser.invoke(SCL::commandLineArguments());
}
```
+ In this case, we add a restricted argument and another required argument with default value.
+ If an argument has an expect value list, only symbols listed in it will be regarded as valid argument.
+ We also tell the parser to show default value and expect values on help list which would be hided if the flags weren't set.
<!-- We could simplify the above code by using the self-return API:
```c++
int main(int /* argc */, char * /* argv */ []) {
SCL::Command rootCommand("work", "What to do on which day?");
rootCommand.addArguments({
SCL::Argument("weekday", "Weekday").expect({
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday",
}),
SCL::Argument("event", "Event to do", false, "football"),
});
rootCommand.help().action([](const SCL::ParseResult &result) {
std::cout << result.value("weekday").toString() << std::endl;
std::cout << result.value("event").toString() << std::endl;
return 0;
});
SCL::Parser parser(rootCommand);
parser.setDisplayOptions(SCL::Parser::ShowArgumentDefaultValue |
SCL::Parser::ShowArgumentExpectedValues);
return parser.invoke(SCL::commandLineArguments());
}
``` -->
Help Text:
```
Description:
What to do on which day?
Usage:
work <weekday> [<event>] [options]
Arguments:
<weekday> Weekday [Expected: "Monday", "Tuesday", "Wednesday", ...]
<event> Event to do [Default: football]
Options:
-h, --help Show help information
```
Test:
```sh
> ./work Monday
Monday
football
```
```sh
> ./work Tuesday basketball
Tuesday
basketball
```
### Option
Simple example of a converter:
```c++
int main(int /* argc */, char * /* argv */ []) {
SCL::Option inputOpt("--input", "Input file");
inputOpt.addArgument(SCL::Argument("file"));
inputOpt.setRequired(true);
SCL::Option outputOpt("--output", "Output file");
outputOpt.addArgument(SCL::Argument("file"));
SCL::Command rootCommand("converter", "Convert A to B.");
rootCommand.addOptions({inputOpt, outputOpt});
rootCommand.addHelpOption();
rootCommand.setHandler([](const SCL::ParseResult &result) {
std::cout << "Input: " << result.valueForOption("--input").toString() << std::endl;
if (result.isOptionSet("--output")) {
std::cout << "Output: " << result.valueForOption("--output").toString() << std::endl;
}
return 0;
});
SCL::Parser parser(rootCommand);
parser.setDisplayOptions(SCL::Parser::ShowOptionalOptionsOnUsage);
return parser.invoke(SCL::commandLineArguments());
}
```
+ In this case, we add two single argument options `input` and `output`, and `input` is required.
+ We also tell the parser to show verbose options on usage.
Help Text:
```
Description:
Convert A to B.
Usage:
converter --input <file> [--output <file>] [-h]
Options:
--input <file> Input file
--output <file> Output file
-h, --help Show help information
```
Test:
```sh
> ./converter --input in
Input: in
```
```sh
> ./converter --input in --output out
Input: in
Output: out
```
### Multi-argument Option
TODO
### Multi-value Argument
TODO
@@ -0,0 +1,4 @@
add_subdirectory(del)
add_subdirectory(mv)
add_subdirectory(git)
add_subdirectory(gcc)
@@ -0,0 +1,7 @@
project(del)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)
@@ -0,0 +1,111 @@
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
namespace zh_CN {
static const char *error_strings[] = {
R"(没有错误。)",
R"(未知的选项 "%1"。)",
R"(未知的命令或参数 "%1"。)",
R"(选项 "%2" 缺少必选参数 "%1"。)",
R"(缺少必选参数 "%1"。)",
R"(参数过多。)",
R"(参数 "%2" 被指定非法的值 "%1"。)",
R"(选项 "%1" 出现在非法的位置,此处应为参数 "%2"。)",
R"(缺少必选选项 "%1"。)",
R"(选项 "%1" 出现次数过多,最多 %2 次。)",
R"(参数 "%2" 被指定非法的值 "%1",应为 "%3"。)",
R"(参数 "%2" 被指定非法的值 "%1",原因: %3)",
R"(选项 "%1" 与 "%2" 是互斥的。)",
R"(选项 "%1" 与其他参数不能同时指定。)",
R"(选项 "%1" 与其他选项不能同时指定。)",
R"(无法读取响应文件 "%1")",
};
static const char *title_strings[] = {
"错误", "用法", "简介", "参数", "选项", "命令", "必选", "默认", "合法值",
};
static const char *role_strings[] = {
"", "调试模式", "显示详细信息", "显示版本信息", "显示帮助信息",
};
static const char *info_strings[] = {
R"("%1" 未匹配。您是否想指定以下之一?)",
};
static const char **strings[] = {
error_strings,
title_strings,
role_strings,
info_strings,
};
static std::string provider(int category, int index) {
return strings[category][index];
}
}
using namespace SysCmdLine;
static int routine(const ParseResult &result) {
auto fileValues = Value::toStringList(result.values("files"));
u8info("将要被删除的文件:\n");
for (const auto &item : std::as_const(fileValues)) {
u8info(" %s\n", item.data());
}
bool prompt = result.isOptionSet("/P");
bool force = result.isOptionSet("/F");
bool subdir = result.isOptionSet("/S");
bool quiet = result.isOptionSet("/Q");
u8info("模式: \n");
if (prompt) {
u8info(" 提示\n");
}
if (force) {
u8info(" 强制\n");
}
if (subdir) {
u8info(" 子文件夹\n");
}
if (quiet) {
u8info(" 静默\n");
}
return 0;
}
int main(int argc, char *argv[]) {
SYSCMDLINE_UNUSED(argc);
SYSCMDLINE_UNUSED(argv);
Option promptOption({"/P", "-p"}, "删除每一个文件之前提示确认");
Option forceOption({"/F", "-f"}, "强制删除只读文件");
Option subdirOption({"/S", "-s"}, "删除所有子目录中的指定的文件");
Option quietOption({"/Q", "-q"}, "安静模式。删除全局通配符时,不要求确认");
Argument fileArg("files", "指定一个或多个文件或者目录列表");
fileArg.setDisplayName("files");
fileArg.setMultiValueEnabled(true);
Command rootCommand("del", {});
rootCommand.addArgument(fileArg);
rootCommand.addOptions({
promptOption,
forceOption,
subdirOption,
quietOption,
});
rootCommand.addHelpOption(false, false, {"/?"});
rootCommand.setHandler(routine);
Parser parser(rootCommand);
parser.setTextProvider(zh_CN::provider);
parser.setDisplayOptions(Parser::ShowOptionalOptionsOnUsage | Parser::ShowArgumentIsRequired |
Parser::AlignAllCatalogues);
return parser.invoke(commandLineArguments(), -1,
Parser::IgnoreOptionCase | Parser::AllowUnixGroupFlags);
}
@@ -0,0 +1,7 @@
project(gcc)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)
@@ -0,0 +1,36 @@
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
using namespace SysCmdLine;
int main(int /* argc */, char * /* argv */[]) {
Option linkDirOption({"-L", "--linkdir"}, "Add link directory");
linkDirOption.setShortMatchRule(Option::ShortMatchSingleChar);
linkDirOption.addArgument(Argument("dir"));
linkDirOption.setUnlimitedOccurrence();
Option includeDirOption({"-I", "--includedir"}, "Add include directory");
includeDirOption.setShortMatchRule(Option::ShortMatchSingleChar);
includeDirOption.addArgument(Argument("dir"));
includeDirOption.setUnlimitedOccurrence();
Command rootCommand("gcc", "GNU C/C++ Compiler");
rootCommand.addOptions({
linkDirOption,
includeDirOption,
});
rootCommand.addVersionOption("0.0.0.1");
rootCommand.addHelpOption(true, true);
rootCommand.setHandler([](const ParseResult &result) {
u8info("Link directories:\n");
for (const auto &item : result.option("-L").allValues())
u8info(" %s\n", item.toString().data());
u8info("Include directories:\n");
for (const auto &item : result.option("-I").allValues())
u8info(" %s\n", item.toString().data());
return 0;
});
Parser parser(rootCommand);
return parser.invoke(commandLineArguments());
}
@@ -0,0 +1,7 @@
project(git)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)
@@ -0,0 +1,39 @@
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
using namespace SysCmdLine;
int main(int /* argc */, char * /* argv */[]) {
Command cloneCommand("clone", "Clone a repository into a new directory");
Command initCommand("init", "Create an empty Git repository or reinitialize an existing one");
Command commitCommand("commit", "Record changes to the repository");
Command mergeCommand("merge", "Join two or more development histories together");
Command rebaseCommand("rebase", "Reapply commits on top of another base tip");
Command rootCommand("git", "Git is a distributed version control system.");
rootCommand.addCommands({
cloneCommand,
initCommand,
commitCommand,
mergeCommand,
rebaseCommand,
});
rootCommand.addVersionOption("0.0.0.1");
rootCommand.addHelpOption(true, true);
rootCommand.setHandler([](const ParseResult &result) {
u8info("%s\n", result.value("weekday").toString().data());
u8info("%s\n", result.value("event").toString().data());
return 0;
});
CommandCatalogue cc;
cc.addCommands("start a working area", {"clone", "init"});
cc.addCommands("grow, mark and tweak your common history", {"commit", "merge", "rebase"});
rootCommand.setCatalogue(cc);
Parser parser(rootCommand);
parser.setDisplayOptions(Parser::ShowArgumentDefaultValue | Parser::ShowArgumentExpectedValues |
Parser::DontShowHelpOnError | Parser::AlignAllCatalogues);
return parser.invoke(SysCmdLine::commandLineArguments());
}
@@ -0,0 +1,7 @@
project(mv)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)
@@ -0,0 +1,24 @@
#include <iostream>
#include <syscmdline/parser.h>
using namespace SysCmdLine;
int main(int argc, char *argv[]) {
Command cmd("mv", "move files to directory");
cmd.addArguments({
Argument("files", "Source files").multi(),
Argument("dir", "Destination directory"),
});
cmd.addHelpOption();
cmd.setHandler([](const ParseResult &result) -> int {
std::cout << "[Sources]" << std::endl;
for (const auto &item : result.values("files")) {
std::cout << item.toString() << std::endl;
}
std::cout << "[Destination]" << std::endl;
std::cout << result.value("dir").toString() << std::endl;
return 0;
});
Parser parser(cmd);
parser.setDisplayOptions(Parser::ShowOptionsHintFront);
return parser.invoke(argc, argv);
}
@@ -0,0 +1,164 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef ARGUMENT_H
#define ARGUMENT_H
#include <functional>
#include <vector>
#include <syscmdline/symbol.h>
#include <syscmdline/value.h>
namespace SysCmdLine {
class ArgumentPrivate;
class SYSCMDLINE_EXPORT Argument : public Symbol {
SYSCMDLINE_DECL_PRIVATE(Argument)
public:
enum Number {
Single,
MultiValue,
Remainder,
};
using Validator = std::function<bool /* result */ (
const std::string & /* token */, Value * /* out */, std::string * /* errorMessage */)>;
Argument();
Argument(const std::string &name, const std::string &desc = {}, bool required = true,
const Value &defaultValue = {});
std::string displayedText() const;
using Symbol::helpText;
std::string helpText(HelpPosition pos, int displayOptions, void *extra) const override;
public:
std::string name() const;
void setName(const std::string &name);
std::string displayName() const;
void setDisplayName(const std::string &displayName);
bool isRequired() const;
void setRequired(bool required);
inline bool isOptional() const;
inline void setOptional(bool optional);
Value defaultValue() const;
void setDefaultValue(const Value &defaultValue);
const std::vector<Value> &expectedValues() const;
void setExpectedValues(const std::vector<Value> &expectedValues);
bool multiValueEnabled() const;
SYSCMDLINE_DECL_DEPRECATED void setMultiValueEnabled(bool on);
Number number() const;
void setNumber(Number valuePolicy);
Validator validator() const;
void setValidator(const Validator &validator);
public:
inline Argument &metavar(const std::string &metavar);
inline Argument &required(bool required = true);
inline Argument &default_value(const Value &value);
inline Argument &expect(const std::vector<Value> &expectedValues);
SYSCMDLINE_DECL_DEPRECATED inline Argument &multi(bool multiValueEnabled = true);
inline Argument &validate(const Validator &validator);
inline Argument &nargs(Number valuePolicy);
};
inline bool Argument::isOptional() const {
return !isRequired();
}
inline void Argument::setOptional(bool optional) {
setRequired(!optional);
}
inline Argument &Argument::metavar(const std::string &metavar) {
setDisplayName(metavar);
return *this;
}
inline Argument &Argument::required(bool required) {
setRequired(required);
return *this;
}
Argument &Argument::default_value(const Value &value) {
setDefaultValue(value);
return *this;
}
Argument &Argument::expect(const std::vector<Value> &expectedValues) {
setExpectedValues(expectedValues);
return *this;
}
inline Argument &Argument::multi(bool multiValueEnabled) {
setMultiValueEnabled(multiValueEnabled);
return *this;
}
inline Argument &Argument::validate(const Argument::Validator &validator) {
setValidator(validator);
return *this;
}
inline Argument &Argument::nargs(const Argument::Number valuePolicy) {
setNumber(valuePolicy);
return *this;
}
class ArgumentHolderPrivate;
class SYSCMDLINE_EXPORT ArgumentHolder : public Symbol {
SYSCMDLINE_DECL_PRIVATE(ArgumentHolder)
public:
std::string displayedArguments(int displayOptions) const;
public:
inline void addArgument(const Argument &argument);
void addArguments(const std::vector<Argument> &arguments);
using Symbol::helpText;
protected:
ArgumentHolder(ArgumentHolderPrivate *d);
};
inline void ArgumentHolder::addArgument(const Argument &argument) {
addArguments({argument});
}
}
#endif // ARGUMENT_H
@@ -0,0 +1,147 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef COMMAND_H
#define COMMAND_H
#include <syscmdline/option.h>
#include <syscmdline/helplayout.h>
namespace SysCmdLine {
class ParseResult;
class CommandCataloguePrivate;
class SYSCMDLINE_EXPORT CommandCatalogue : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(CommandCatalogue)
public:
CommandCatalogue();
public:
void addArguments(const std::string &name, const std::vector<std::string> &args);
void addOptions(const std::string &name, const std::vector<std::string> &options);
void addCommands(const std::string &name, const std::vector<std::string> &commands);
};
class CommandPrivate;
class SYSCMDLINE_EXPORT Command : public ArgumentHolder {
SYSCMDLINE_DECL_PRIVATE(Command)
public:
using Handler = std::function<int /* code */ (const ParseResult & /* result */)>;
Command();
Command(const std::string &name, const std::string &desc = {},
const std::vector<Argument> &args = {});
using Symbol::helpText;
std::string helpText(HelpPosition pos, int displayOptions, void *extra) const override;
public:
std::string name() const;
void setName(const std::string &name);
inline void addOption(const Option &option, const std::string &group = {});
void addOptions(const std::vector<Option> &options, const std::string &group = {});
int commandCount() const;
Command command(int index) const;
inline void addCommand(const Command &command);
void addCommands(const std::vector<Command> &commands);
std::string detailedDescription() const;
void setDetailedDescription(const std::string &detailedDescription);
Handler handler() const;
void setHandler(const Handler &handler);
CommandCatalogue catalogue() const;
void setCatalogue(const CommandCatalogue &catalogue);
std::string versionString() const;
void addVersionOption(const std::string &version,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
void addHelpOption(bool showHelpIfNoArg = false, bool global = false,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
HelpLayout helpLayout() const;
void setHelpLayout(const HelpLayout &helpLayout);
public:
inline Command &detailed(const std::string &detailedDescription);
inline Command &action(const Handler &handler);
inline Command &catalog(const CommandCatalogue &catalogue);
inline Command &version(const std::string &version,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
inline Command &help(bool showHelpIfNoArg = false, bool global = false,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
};
inline void Command::addCommand(const Command &command) {
addCommands({command});
}
inline void Command::addOption(const Option &option, const std::string &group) {
addOptions({option}, group);
}
inline Command &Command::detailed(const std::string &detailedDescription) {
setDetailedDescription(detailedDescription);
return *this;
}
inline Command &Command::action(const Command::Handler &handler) {
setHandler(handler);
return *this;
}
inline Command &Command::catalog(const CommandCatalogue &catalogue) {
setCatalogue(catalogue);
return *this;
}
inline Command &Command::version(const std::string &version,
const std::vector<std::string> &tokens,
const std::string &desc) {
addVersionOption(version, tokens, desc);
return *this;
}
inline Command &Command::help(bool showHelpIfNoArg, bool global,
const std::vector<std::string> &tokens, const std::string &desc) {
addHelpOption(showHelpIfNoArg, global, tokens, desc);
return *this;
}
}
#endif // COMMAND_H
@@ -0,0 +1,71 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef GLOBAL_H
#define GLOBAL_H
// Export define
#ifdef _WIN32
# define SYSCMDLINE_DECL_EXPORT __declspec(dllexport)
# define SYSCMDLINE_DECL_IMPORT __declspec(dllimport)
#else
# define SYSCMDLINE_DECL_EXPORT __attribute__((visibility("default")))
# define SYSCMDLINE_DECL_IMPORT __attribute__((visibility("default")))
#endif
#ifdef SYSCMDLINE_STATIC
# define SYSCMDLINE_EXPORT
#else
# ifdef SYSCMDLINE_LIBRARY
# define SYSCMDLINE_EXPORT SYSCMDLINE_DECL_EXPORT
# else
# define SYSCMDLINE_EXPORT SYSCMDLINE_DECL_IMPORT
# endif
#endif
#ifdef _WIN32
# define SYSCMDLINE_DECL_DEPRECATED __declspec(deprecated)
#else
# define SYSCMDLINE_DECL_DEPRECATED __attribute__((__deprecated__))
#endif
// Utils
#define SYSCMDLINE_UNUSED(X) (void) X;
#define SYSCMDLINE_DECL_PRIVATE(X) \
public: \
const X##Private *d_func() const { \
return reinterpret_cast<const X##Private *>(d_ptr); \
}
#if defined(__GNUC__) || defined(__clang__)
# define SYSCMDLINE_PRINTF_FORMAT(fmtpos, attrpos) \
__attribute__((__format__(__printf__, fmtpos, attrpos)))
#else
# define SYSCMDLINE_PRINTF_FORMAT(fmtpos, attrpos)
#endif
#endif // GLOBAL_H
@@ -0,0 +1,109 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef HELPLAYOUT_H
#define HELPLAYOUT_H
#include <string>
#include <vector>
#include <functional>
#include <syscmdline/sharedbase.h>
namespace SysCmdLine {
class Parser;
class HelpLayoutPrivate;
class SYSCMDLINE_EXPORT HelpLayout : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(HelpLayout)
public:
HelpLayout();
enum HelpTextItem {
HT_Prologue,
HT_Epilogue,
HT_Description,
HT_Usage,
};
enum HelpListItem {
HL_Arguments,
HL_Options,
HL_Commands,
};
enum MessageItem {
MI_Information,
MI_Warning,
MI_Critical,
};
struct Text {
std::string title;
std::string lines;
};
struct List {
std::string title;
std::vector<std::string> firstColumn;
std::vector<std::string> secondColumn;
};
struct Context {
const Parser *parser;
union {
// Available on `HelpTextItem`, `UserHelpTextItem`
const Text *text;
// Available on `HelpListItem`, `UserHelpListItem`
struct {
const List *list;
int firstColumnLength;
};
};
bool hasNext;
};
using Output = std::function<void(const Context & /* context */)>;
public:
void addHelpTextItem(HelpTextItem type, const Output &out = {});
void addHelpListItem(HelpListItem type, const Output &out = {});
void addMessageItem(MessageItem type, const Output &out = {});
void addUserHelpTextItem(const Text &text, const Output &out = {});
void addUserHelpListItem(const List &list, const Output &out = {});
void addUserIntroItem(const Output &out);
public:
static HelpLayout defaultHelpLayout();
};
}
#endif // HELPLAYOUT_H
@@ -0,0 +1,178 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef OPTION_H
#define OPTION_H
#include <syscmdline/argument.h>
namespace SysCmdLine {
class OptionPrivate;
class SYSCMDLINE_EXPORT Option : public ArgumentHolder {
SYSCMDLINE_DECL_PRIVATE(Option)
public:
enum Role {
NoRole,
Debug,
Verbose,
Version,
Help,
};
enum PriorLevel {
NoPrior,
IgnoreMissingArguments,
IgnoreMissingSymbols,
AutoSetWhenNoSymbols,
ExclusiveToArguments,
ExclusiveToOptions,
ExclusiveToAll,
};
enum ShortMatchRule {
NoShortMatch,
ShortMatchSingleLetter,
ShortMatchSingleChar,
ShortMatchAll,
};
Option();
Option(Role role, const std::vector<std::string> &tokens = {}, const std::string &desc = {});
Option(const std::string &token, const std::string &desc = {}, const Argument &arg = {},
bool required = false);
Option(const std::vector<std::string> &tokens, const std::string &desc = {},
const Argument &arg = {}, bool required = false);
inline Option(std::initializer_list<std::string> tokens, const std::string &desc = {},
const Argument &arg = {}, bool required = false);
using Symbol::helpText;
std::string helpText(HelpPosition pos, int displayOptions, void *extra) const override;
public:
Role role() const;
void setRole(Role role);
const std::vector<std::string> &tokens() const;
inline std::string token() const;
void setTokens(const std::vector<std::string> &tokens);
inline void setToken(const std::string &token);
bool isRequired() const;
void setRequired(bool required);
inline bool isOptional() const;
inline void setOptional(bool optional);
ShortMatchRule shortMatchRule() const;
void setShortMatchRule(ShortMatchRule shortMatchRule);
PriorLevel priorLevel() const;
void setPriorLevel(PriorLevel priorLevel);
bool isGlobal() const;
void setGlobal(bool on);
int maxOccurrence() const;
void setMaxOccurrence(int max);
inline void setUnlimitedOccurrence();
public:
inline Option &arg(const std::string &name, bool required = true,
const Value &defaultValue = {});
inline Option &arg(const Argument &arg);
inline Option &required(bool required = true);
inline Option &short_match(ShortMatchRule shortMatchRule = ShortMatchAll);
inline Option &prior(PriorLevel priorLevel);
inline Option &global(bool global = true);
inline Option &multi(int maxOccurrence = 0);
};
inline Option::Option(std::initializer_list<std::string> tokens, const std::string &desc,
const Argument &arg, bool required)
: Option(std::vector<std::string>(tokens), desc, arg, required) {
}
inline std::string Option::token() const {
return tokens().front();
}
inline void Option::setToken(const std::string &token) {
setTokens({token});
}
inline bool Option::isOptional() const {
return !isRequired();
}
inline void Option::setOptional(bool optional) {
setRequired(!optional);
}
inline void Option::setUnlimitedOccurrence() {
setMaxOccurrence(0);
}
inline Option &Option::arg(const std::string &name, bool required, const Value &defaultValue) {
// Option's argument doesn't need a description
addArgument(Argument(name, {}, required, defaultValue));
return *this;
}
inline Option &Option::arg(const Argument &arg) {
addArgument(arg);
return *this;
}
inline Option &Option::required(bool required) {
setRequired(required);
return *this;
}
inline Option &Option::short_match(Option::ShortMatchRule shortMatchRule) {
setShortMatchRule(shortMatchRule);
return *this;
}
inline Option &Option::prior(Option::PriorLevel priorLevel) {
setPriorLevel(priorLevel);
return *this;
}
inline Option &Option::global(bool global) {
setGlobal(global);
return *this;
}
inline Option &Option::multi(int maxOccurrence) {
setMaxOccurrence(maxOccurrence);
return *this;
}
}
#endif // OPTION_H
@@ -0,0 +1,121 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef PARSER_H
#define PARSER_H
#include <syscmdline/parseresult.h>
namespace SysCmdLine {
class ParserPrivate;
class SYSCMDLINE_EXPORT Parser : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(Parser)
public:
Parser();
Parser(const Command &rootCommand);
public:
enum ParseOption {
Standard = 0,
IgnoreCommandCase = 0x1,
IgnoreOptionCase = 0x2,
AllowUnixGroupFlags = 0x4,
AllowDosShortOptions = 0x8,
DontAllowUnixShortOptions = 0x10,
EnableResponseFile = 0x20,
};
enum DisplayOption {
Normal = 0,
DontShowHelpOnError = 0x1,
DontShowIntroOnError = 0x2,
SkipCorrection = 0x4,
ShowArgumentDefaultValue = 0x8,
ShowArgumentExpectedValues = 0x10,
ShowArgumentIsRequired = 0x20,
ShowOptionIsRequired = 0x40,
AlignAllCatalogues = 0x80,
DontShowRequiredOptionsOnUsage = 0x100,
ShowOptionalOptionsOnUsage = 0x200,
ShowOptionsBehindArguments = 0x400,
ShowOptionsHintFront = 0x800,
};
std::string prologue() const;
void setPrologue(const std::string &prologue);
std::string epilogue() const;
void setEpilogue(const std::string &epilogue);
int displayOptions() const;
void setDisplayOptions(int displayOptions);
Command rootCommand() const;
void setRootCommand(const Command &rootCommand);
ParseResult parse(const std::vector<std::string> &args, int parseOptions = Standard);
inline int invoke(const std::vector<std::string> &args, int errCode = -1,
int parseOptions = Standard);
// Don't use this pair of API on Windows because the arguemnts passed by `main`
// entry is in ANSI encoding, but the library uses UTF-8.
inline ParseResult parse(int argc, char **argv, int parseOptions = Standard);
inline int invoke(int argc, char **argv, int errCode = -1, int parseOptions = Standard);
public:
using TextProvider = std::string (*)(int /* category */, int /* index */);
enum SizeType {
ST_Indent,
ST_Spacing,
ST_ConsoleWidth,
};
int size(SizeType sizeType) const;
void setSize(SizeType sizeType, int value);
TextProvider textProvider();
void setTextProvider(TextProvider textProvider);
static TextProvider defaultTextProvider();
};
inline int Parser::invoke(const std::vector<std::string> &args, int errCode, int parseOptions) {
return parse(args, parseOptions).invoke(errCode);
}
inline ParseResult Parser::parse(int argc, char **argv, int parseOptions) {
return parse({argv, argv + argc}, parseOptions);
}
inline int Parser::invoke(int argc, char **argv, int errCode, int parseOptions) {
return parse({argv, argv + argc}, parseOptions).invoke(errCode);
}
}
#endif // PARSER_H
@@ -0,0 +1,279 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef PARSERESULT_H
#define PARSERESULT_H
#include <syscmdline/command.h>
namespace SysCmdLine {
class Parser;
class ParseResultPrivate;
class SYSCMDLINE_EXPORT OptionResult {
public:
OptionResult();
public:
Option option() const;
int indexOf(const std::string &name) const;
int count() const;
inline bool isSet() const;
inline bool isArgumentSet(const Argument &arg, int n = 0) const;
inline bool isArgumentSet(const std::string &name, int n = 0) const;
inline bool isArgumentSet(int index, int n = 0) const;
// Get all values of an argument
inline std::vector<Value> allValues(const Argument &arg) const;
inline std::vector<Value> allValues(const std::string &name) const;
std::vector<Value> allValues(int index = 0) const;
// Get values of multi-value argument at the option's N-th occurrence
inline const std::vector<Value> &values(const Argument &arg, int n = 0) const;
inline const std::vector<Value> &values(const std::string &name, int n = 0) const;
const std::vector<Value> &values(int index = 0, int n = 0) const;
// Get value of single-value argument at the option's N-th occurrence or its default value
inline Value value(const Argument &arg, int n = 0) const;
inline Value value(const std::string &name, int n = 0) const;
Value value(int index = 0, int n = 0) const;
private:
inline OptionResult(const void *data);
const void *data;
friend class ParseResult;
};
inline bool OptionResult::isSet() const {
return count() > 0;
}
inline bool OptionResult::isArgumentSet(const Argument &arg, int n) const {
return !values(indexOf(arg.name()), n).empty();
}
inline bool OptionResult::isArgumentSet(const std::string &name, int n) const {
return !values(indexOf(name), n).empty();
}
inline bool OptionResult::isArgumentSet(int index, int n) const {
return !values(index, n).empty();
}
inline std::vector<Value> OptionResult::allValues(const Argument &arg) const {
return allValues(indexOf(arg.name()));
}
inline std::vector<Value> OptionResult::allValues(const std::string &name) const {
return allValues(indexOf(name));
}
inline const std::vector<Value> &OptionResult::values(const Argument &arg, int n) const {
return values(indexOf(arg.name()), n);
}
inline const std::vector<Value> &OptionResult::values(const std::string &name, int n) const {
return values(indexOf(name), n);
}
inline Value OptionResult::value(const Argument &arg, int n) const {
return value(indexOf(arg.name()), n);
}
inline Value OptionResult::value(const std::string &name, int n) const {
return value(indexOf(name), n);
}
inline OptionResult::OptionResult(const void *data) : data(data) {
}
class SYSCMDLINE_EXPORT ParseResult : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(ParseResult)
public:
ParseResult();
inline bool isValid() const;
public:
Command rootCommand() const;
const std::vector<std::string> &arguments() const;
int invoke(int errCode = -1) const;
int dispatch() const;
public:
enum Error {
NoError,
UnknownOption,
UnknownCommand,
MissingOptionArgument,
MissingCommandArgument,
TooManyArguments,
InvalidArgumentValue,
InvalidOptionPosition,
MissingRequiredOption,
OptionOccurTooMuch,
ArgumentTypeMismatch,
ArgumentValidateFailed,
MutuallyExclusiveOptions,
PriorOptionWithArguments,
PriorOptionWithOptions,
ErrorReadingResponseFile,
};
Error error() const;
std::string errorText() const;
std::string correctionText() const;
std::string cancellationToken() const;
Command command() const;
std::vector<Option> globalOptions() const;
std::vector<int> commandIndexStack() const;
int indexOfArgument(const std::string &name) const;
int indexOfOption(const std::string &token) const;
void showError() const;
void showHelpText() const;
void showMessage(const std::string &info, const std::string &warning,
const std::string &error) const;
public:
inline bool isVersionSet() const;
inline bool isHelpSet() const;
bool isRoleSet(Option::Role role) const;
inline bool isArgumentSet(const Argument &arg) const;
inline bool isArgumentSet(const std::string &name) const;
inline bool isArgumentSet(int index) const;
// Get values of multi-value argument
inline const std::vector<Value> &values(const Argument &arg) const;
inline const std::vector<Value> &values(const std::string &name) const;
const std::vector<Value> &values(int index) const;
// Get value of single-value argument or its default value
inline Value value(const Argument &arg) const;
inline Value value(const std::string &name) const;
Value value(int index) const;
inline bool isOptionSet(const Option &option) const;
inline bool isOptionSet(const std::string &token) const;
inline bool isOptionSet(int index) const;
// Detailed result for an option
inline OptionResult option(const Option &option) const;
inline OptionResult option(const std::string &token) const;
OptionResult option(int index) const;
// Get value of single-value argument of given option or its default value
inline Value valueForOption(const Option &option) const;
inline Value valueForOption(const std::string &token) const;
inline Value valueForOption(int index) const;
protected:
ParseResult(ParseResultPrivate *d);
friend class Parser;
};
inline bool ParseResult::isValid() const {
return d_ptr != nullptr;
}
inline bool ParseResult::isVersionSet() const {
return isRoleSet(Option::Version);
}
inline bool ParseResult::isHelpSet() const {
return isRoleSet(Option::Help);
}
inline bool ParseResult::isArgumentSet(const Argument &arg) const {
return isArgumentSet(indexOfArgument(arg.name()));
}
inline bool ParseResult::isArgumentSet(const std::string &name) const {
return isArgumentSet(indexOfArgument(name));
}
inline bool ParseResult::isArgumentSet(int index) const {
return !values(index).empty();
}
inline const std::vector<Value> &ParseResult::values(const Argument &arg) const {
return values(indexOfArgument(arg.name()));
}
inline const std::vector<Value> &ParseResult::values(const std::string &name) const {
return values(indexOfArgument(name));
}
inline Value ParseResult::value(const Argument &arg) const {
return value(indexOfArgument(arg.name()));
}
inline Value ParseResult::value(const std::string &name) const {
return value(indexOfArgument(name));
}
inline bool ParseResult::isOptionSet(const Option &option) const {
return this->option(indexOfOption(option.token())).count() > 0;
}
inline bool ParseResult::isOptionSet(const std::string &token) const {
return option(indexOfOption(token)).count() > 0;
}
inline bool ParseResult::isOptionSet(int index) const {
return option(index).count() > 0;
}
inline OptionResult ParseResult::option(const Option &option) const {
return this->option(indexOfOption(option.token()));
}
inline OptionResult ParseResult::option(const std::string &token) const {
return option(indexOfOption(token));
}
inline Value ParseResult::valueForOption(const Option &option) const {
return this->option(indexOfOption(option.token())).value();
}
inline Value ParseResult::valueForOption(const std::string &token) const {
return option(indexOfOption(token)).value();
}
inline Value ParseResult::valueForOption(int index) const {
return option(index).value();
}
}
#endif // PARSERESULT_H
@@ -0,0 +1,72 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef SHAREDBASE_H
#define SHAREDBASE_H
#include <utility>
#include <syscmdline/global.h>
namespace SysCmdLine {
class SharedBasePrivate;
class SYSCMDLINE_EXPORT SharedBase {
SYSCMDLINE_DECL_PRIVATE(SharedBase)
public:
virtual ~SharedBase();
SharedBase(const SharedBase &other);
inline SharedBase(SharedBase &&other) noexcept;
SharedBase &operator=(const SharedBase &other);
inline SharedBase &operator=(SharedBase &&other) noexcept;
public:
inline void swap(SharedBase &other) noexcept;
bool isDetached() const;
void detach();
protected:
SharedBase(SharedBasePrivate *d);
SharedBasePrivate *d_ptr;
};
inline SharedBase::SharedBase(SharedBase &&other) noexcept : d_ptr(nullptr) {
swap(other);
}
inline SharedBase &SharedBase::operator=(SharedBase &&other) noexcept {
swap(other);
return *this;
}
void SharedBase::swap(SharedBase &other) noexcept {
std::swap(d_ptr, other.d_ptr);
}
}
#endif // SHAREDBASE_H
@@ -0,0 +1,75 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef STRINGS_H
#define STRINGS_H
namespace SysCmdLine {
namespace Strings {
enum TextCategory {
ParseError,
Title,
OptionRole,
Information,
Token,
};
enum TitleText {
Error,
Usage,
Description,
Arguments,
Options,
Commands,
Required,
Default,
ExpectedValues,
};
enum OptionRoleText {
NoRole,
Debug,
Verbose,
Version,
Help,
};
enum InformationText {
MatchCommand,
};
enum TokenText {
OptionalCommands,
OptionalOptions,
};
}
}
#endif // STRINGS_H
@@ -0,0 +1,81 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef SYMBOL_H
#define SYMBOL_H
#include <functional>
#include <string>
#include <syscmdline/sharedbase.h>
namespace SysCmdLine {
class SymbolPrivate;
class SYSCMDLINE_EXPORT Symbol : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(Symbol)
public:
enum SymbolType {
ST_Command,
ST_Option,
ST_Argument,
};
enum HelpPosition {
HP_Usage,
HP_ErrorText,
HP_FirstColumn,
HP_SecondColumn,
};
using HelpProvider =
std::function<std::string(const Symbol * /* this */, HelpPosition /* pos */,
int /* displayOptions */, void * /* extra */)>;
inline std::string helpText(HelpPosition pos, int displayOptions) const;
virtual std::string helpText(HelpPosition pos, int displayOptions, void *extra) const;
public:
SymbolType type() const;
std::string description() const;
void setDescription(const std::string &desc);
HelpProvider helpProvider() const;
void setHelpProvider(const HelpProvider &helpProvider);
protected:
Symbol(SymbolPrivate *d);
};
inline std::string Symbol::helpText(Symbol::HelpPosition pos, int displayOptions) const {
return helpText(pos, displayOptions, nullptr);
}
}
#endif // SYMBOL_H
@@ -0,0 +1,83 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef SYSTEM_H
#define SYSTEM_H
#include <string>
#include <vector>
#include <syscmdline/global.h>
namespace SysCmdLine {
SYSCMDLINE_EXPORT std::string wideToUtf8(const std::wstring &s);
SYSCMDLINE_EXPORT std::wstring utf8ToWide(const std::string &s);
#ifdef _WIN32
SYSCMDLINE_EXPORT std::string ansiToUtf8(const std::string &s);
#endif
SYSCMDLINE_EXPORT std::string appPath();
SYSCMDLINE_EXPORT std::string appDirectory();
SYSCMDLINE_EXPORT std::string appFileName();
SYSCMDLINE_EXPORT std::string appName();
SYSCMDLINE_EXPORT std::vector<std::string> commandLineArguments();
enum ConsoleColor {
DefaultColor = -1,
Black = 0x0,
Red = 0x1,
Green = 0x2,
Blue = 0x4,
Yellow = Red | Green,
Purple = Red | Blue,
Cyan = Green | Blue,
White = Red | Green | Blue,
Intensified = 0x100,
};
int u8printf(int foreground, int background, const char *fmt, ...)
SYSCMDLINE_PRINTF_FORMAT(3, 4);
int u8vprintf(int foreground, int background, const char *fmt, va_list args);
enum MessageType {
MT_Debug,
MT_Message,
MT_Healthy,
MT_Warning,
MT_Critical,
};
SYSCMDLINE_EXPORT int u8debug(MessageType messageType, bool highlight, const char *fmt, ...)
SYSCMDLINE_PRINTF_FORMAT(3, 4);
SYSCMDLINE_EXPORT int u8info(const char *fmt, ...) SYSCMDLINE_PRINTF_FORMAT(1, 2);
}
#endif // SYSTEM_H
@@ -0,0 +1,120 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef VALUE_H
#define VALUE_H
#include <string>
#include <vector>
#include <cstdint>
#include <syscmdline/global.h>
namespace SysCmdLine {
class SYSCMDLINE_EXPORT Value {
public:
enum Type {
Null,
Bool,
Int,
Int64,
Double,
String,
};
Value(Type type = Null);
inline Value(bool b);
inline Value(int i);
inline Value(int64_t l);
inline Value(double d);
inline Value(const std::string &s);
inline Value(const char *ch, int size = -1);
~Value();
Value(const Value &other);
Value(Value &&other) noexcept;
Value &operator=(const Value &other);
Value &operator=(Value &&other) noexcept;
inline Type type() const;
bool isEmpty() const;
bool toBool() const;
int toInt() const;
int64_t toInt64() const;
double toDouble() const;
std::string toString() const;
bool operator==(const Value &other) const;
bool operator!=(const Value &other) const;
static Value fromString(const std::string &s, Type type);
static std::vector<std::string> toStringList(const std::vector<Value> &values);
static const char *typeName(Type type);
protected:
union {
bool b;
int i;
int64_t l;
double d;
std::string *s;
} data;
Type _type;
};
inline Value::Value(bool b) : _type(Bool) {
data.b = b;
}
inline Value::Value(int i) : _type(Int) {
data.i = i;
}
inline Value::Value(int64_t l) : _type(Int64) {
data.l = l;
}
inline Value::Value(double d) : _type(Double) {
data.d = d;
}
inline Value::Value(const std::string &s) : _type(String) {
data.s = new std::string(s);
}
inline Value::Value(const char *ch, int size) : _type(String) {
data.s = size >= 0 ? new std::string(ch, size) : new std::string(ch);
}
inline Value::Type Value::type() const {
return _type;
}
}
#endif // VALUE_H
@@ -0,0 +1,312 @@
#include "argument.h"
#include "argument_p.h"
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
# include <stdexcept>
#endif
#include <algorithm>
#include "strings.h"
#include "parser.h"
#include "utils_p.h"
namespace SysCmdLine {
ArgumentPrivate::ArgumentPrivate(std::string name, const std::string &desc, bool required,
Value defaultValue)
: SymbolPrivate(Symbol::ST_Argument, desc), name(std::move(name)), required(required),
defaultValue(std::move(defaultValue)), number(Argument::Single) {
}
SymbolPrivate *ArgumentPrivate::clone() const {
return new ArgumentPrivate(*this);
}
Argument::Argument() : Argument(std::string()) {
}
Argument::Argument(const std::string &name, const std::string &desc, bool required,
const Value &defaultValue)
: Symbol(new ArgumentPrivate(name, desc, required, defaultValue)) {
}
std::string Argument::displayedText() const {
Q_D2(Argument);
std::string res = d->displayName.empty() ? ("<" + d->name + ">") : d->displayName;
return res;
}
std::string Argument::helpText(Symbol::HelpPosition pos, int displayOptions,
void *extra) const {
Q_D2(Argument);
if (auto ss = Symbol::helpText(pos, displayOptions, extra); !ss.empty()) {
return ss;
}
switch (pos) {
case Symbol::HP_Usage: {
return displayedText() + ((d->number != Single) ? "..." : "");
}
case Symbol::HP_SecondColumn: {
auto textProvider = reinterpret_cast<Parser::TextProvider>(extra);
if (!textProvider) {
textProvider = Parser::defaultTextProvider();
}
std::string appendix;
// Required
if (d->required && (displayOptions & Parser::ShowArgumentIsRequired)) {
appendix += " [" + textProvider(Strings::Title, Strings::Required) + "]";
}
// Default Value
if (d->defaultValue.type() != Value::Null &&
(displayOptions & Parser::ShowArgumentDefaultValue)) {
appendix += " [" + textProvider(Strings::Title, Strings::Default) + ": " +
d->defaultValue.toString() + "]";
}
// Expected Values
if (!d->expectedValues.empty() &&
(displayOptions & Parser::ShowArgumentExpectedValues)) {
std::vector<std::string> values;
values.reserve(d->expectedValues.size());
for (const auto &item : d->expectedValues) {
switch (item.type()) {
case Value::String:
values.push_back("\"" + item.toString() + "\"");
break;
default:
values.push_back(item.toString());
break;
}
}
appendix += " [" + textProvider(Strings::Title, Strings::ExpectedValues) +
": " + Utils::join(values, ", ") + "]";
}
return d->desc + appendix;
}
default:
break;
}
return displayedText();
}
std::string Argument::name() const {
Q_D2(Argument);
return d->name;
}
void Argument::setName(const std::string &name) {
Q_D(Argument);
d->name = name;
}
std::string Argument::displayName() const {
Q_D2(Argument);
return d->displayName;
}
void Argument::setDisplayName(const std::string &displayName) {
Q_D(Argument);
d->displayName = displayName;
}
bool Argument::isRequired() const {
Q_D2(Argument);
return d->required;
}
void Argument::setRequired(bool required) {
Q_D(Argument);
d->required = required;
}
Value Argument::defaultValue() const {
Q_D2(Argument);
return d->defaultValue;
}
void Argument::setDefaultValue(const Value &defaultValue) {
Q_D(Argument);
d->defaultValue = defaultValue;
}
const std::vector<Value> &Argument::expectedValues() const {
Q_D2(Argument);
return d->expectedValues;
}
void Argument::setExpectedValues(const std::vector<Value> &expectedValues) {
Q_D(Argument);
d->expectedValues = expectedValues;
}
bool Argument::multiValueEnabled() const {
Q_D2(Argument);
return d->number != Single;
}
void Argument::setMultiValueEnabled(bool on) {
Q_D(Argument);
d->number = on ? MultiValue : Single;
}
Argument::Number Argument::number() const {
Q_D2(Argument);
return d->number;
}
void Argument::setNumber(Number number) {
Q_D(Argument);
d->number = number;
}
Argument::Validator Argument::validator() const {
Q_D2(Argument);
return d->validator;
}
void Argument::setValidator(const Validator &validator) {
Q_D(Argument);
d->validator = validator;
}
ArgumentHolderPrivate::ArgumentHolderPrivate(Symbol::SymbolType type, const std::string &desc)
: SymbolPrivate(type, desc) {
}
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void ArgumentHolderPrivate::checkAddedArgument(const Argument &arg) const {
const auto &d = arg.d_func();
const auto &name = d->name;
// Empty argument name?
if (name.empty()) {
throw std::runtime_error("argument doesn't have a name");
}
// Duplicated argument name?
if (std::any_of(arguments.begin(), arguments.end(), [&name](const Argument &arg) {
return arg.name() == name; //
})) {
throw std::runtime_error(Utils::formatText("argument name \"%1\" duplicated", {name}));
}
// Required argument behind optional one?
if (!arguments.empty() && arguments.back().isOptional() && d->required) {
throw std::runtime_error("required argument after required arguments is prohibited");
}
bool hasMultiValueArgument =
std::any_of(arguments.begin(), arguments.end(), [&name](const Argument &arg) {
return arg.multiValueEnabled(); //
});
if (arg.multiValueEnabled()) {
// Multiple multi-value argument?
if (hasMultiValueArgument) {
throw std::runtime_error("at most one multi-value argument");
}
hasMultiValueArgument = true;
} else if (hasMultiValueArgument && !d->required) {
// Optional argument after multi-value argument?
throw std::runtime_error("required argument after multi-value argument is prohibited");
}
{
const auto &expectedValues = d->expectedValues;
const auto &defaultValue = d->defaultValue;
if (!expectedValues.empty()) {
// Null expected value?
for (size_t i = 0; i < expectedValues.size(); ++i) {
if (expectedValues[i].type() == Value::Null) {
throw std::runtime_error(
Utils::formatText("expected value at %1 is null", {std::to_string(i)}));
}
}
// Invalid default value?
if (defaultValue.type() != Value::Null &&
std::find(expectedValues.begin(), expectedValues.end(), defaultValue) ==
expectedValues.end()) {
throw std::runtime_error(Utils::formatText(
"default value \"%1\" is not in expect values", {defaultValue.toString()}));
}
}
const auto &validator = d->validator;
if (validator && defaultValue.type() != Value::Null) {
// Validator is incompatible with the default value?
Value val;
std::string errorMessage;
auto res = validator(defaultValue.toString(), &val, &errorMessage);
if (!res) {
throw std::runtime_error("validator is not able to handle the default value.");
}
}
}
// ...
}
#endif
std::string ArgumentHolder::displayedArguments(int displayOptions) const {
Q_D2(ArgumentHolder);
// Use C Style to traverse
auto arguments = d->arguments.data();
int size = int(d->arguments.size());
std::string ss;
int optionalIdx = size;
for (int i = 0; i < size; ++i) {
if (!arguments[i].isRequired()) {
optionalIdx = i;
break;
}
}
if (optionalIdx > 0) {
for (int i = 0; i < optionalIdx - 1; ++i) {
ss += arguments[i].helpText(Symbol::HP_Usage, displayOptions, nullptr);
ss += " ";
}
ss += arguments[optionalIdx - 1].helpText(Symbol::HP_Usage, displayOptions, nullptr);
}
if (optionalIdx < size) {
if (optionalIdx > 0)
ss += " ";
ss += "[";
for (int i = optionalIdx; i < size - 1; ++i) {
ss += arguments[i].helpText(Symbol::HP_Usage, displayOptions, nullptr);
ss += " ";
}
ss += arguments[size - 1].helpText(Symbol::HP_Usage, displayOptions, nullptr);
ss += "]";
}
return ss;
}
void ArgumentHolder::addArguments(const std::vector<Argument> &arguments) {
Q_D(ArgumentHolder);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (const auto &arg : arguments) {
d->checkAddedArgument(arg);
d->arguments.push_back(arg);
}
#else
// d->arguments.insert(d->arguments.end(), arguments.begin(), arguments.end());
d->arguments = Utils::concatVector(d->arguments, arguments);
#endif
}
ArgumentHolder::ArgumentHolder(ArgumentHolderPrivate *d) : Symbol(d) {
}
}
@@ -0,0 +1,40 @@
#ifndef ARGUMENT_P_H
#define ARGUMENT_P_H
#include "argument.h"
#include "symbol_p.h"
namespace SysCmdLine {
class ArgumentPrivate : public SymbolPrivate {
public:
ArgumentPrivate(std::string name, const std::string &desc, bool required,
Value defaultValue);
SymbolPrivate *clone() const override;
public:
std::string name;
bool required;
Value defaultValue;
std::vector<Value> expectedValues;
std::string displayName;
Argument::Number number;
Argument::Validator validator;
};
class ArgumentHolderPrivate : public SymbolPrivate {
public:
ArgumentHolderPrivate(Symbol::SymbolType type, const std::string &desc);
public:
std::vector<Argument> arguments;
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void checkAddedArgument(const Argument &arg) const;
#endif
};
}
#endif // ARGUMENT_P_H
@@ -0,0 +1,480 @@
#include "command.h"
#include "command_p.h"
#include <algorithm>
#include <utility>
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
# include <stdexcept>
# include "option_p.h"
#endif
#include "parser.h"
#include "utils_p.h"
#include "option_p.h"
#include "strings.h"
namespace SysCmdLine {
SharedBasePrivate *CommandCataloguePrivate::clone() const {
return new CommandCataloguePrivate(*this);
}
static void addIndexes(GenericMap &indexes, StringList &keys, const std::string &key,
const StringList &val) {
auto it = indexes.find(key);
if (it == indexes.end()) {
indexes[key] = ele(new StringList(val));
keys.push_back(key);
return;
}
auto list = *it->second.sl;
// list.insert(list.end(), val.begin(), val.end());
list = Utils::concatVector(list, val);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (size_t i = 0; i < list.size(); ++i) {
for (size_t j = 0; j < i; ++j) {
if (list[i] == list[j]) {
throw std::runtime_error(Utils::formatText(
R"(duplicated items "%1" and "%2" in catalogue)", {
list[i],
list[j],
}));
}
}
}
#endif
}
CommandCatalogue::CommandCatalogue() : SharedBase(new CommandCataloguePrivate()) {
}
void CommandCatalogue::addArguments(const std::string &name, const StringList &args) {
Q_D(CommandCatalogue);
addIndexes(d->arg.data, d->arguments, name, args);
}
void CommandCatalogue::addOptions(const std::string &name, const StringList &options) {
Q_D(CommandCatalogue);
addIndexes(d->opt.data, d->options, name, options);
}
void CommandCatalogue::addCommands(const std::string &name, const StringList &commands) {
Q_D(CommandCatalogue);
addIndexes(d->cmd.data, d->commands, name, commands);
}
CommandPrivate::CommandPrivate(std::string name, const std::string &desc)
: ArgumentHolderPrivate(Symbol::ST_Command, desc), name(std::move(name)) {
}
SharedBasePrivate *CommandPrivate::clone() const {
return new CommandPrivate(*this);
}
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void CommandPrivate::checkAddedCommand(const Command &cmd) const {
const auto &name = cmd.name();
// Empty command name?
if (name.empty()) {
throw std::runtime_error("command doesn't have a name");
}
// Duplicated command name?
if (std::any_of(commands.begin(), commands.end(), [&name](const Command &command) {
return command.name() == name; //
})) {
throw std::runtime_error(Utils::formatText("command name \"%1\" duplicated", {name}));
}
}
void CommandPrivate::checkAddedOption(const Option &opt,
const std::string &exclusiveGroup) const {
const auto &d = opt.d_func();
// Empty token?
if (d->tokens.empty()) {
throw std::runtime_error("option doesn't have any token");
}
for (const auto &token : d->tokens) {
// empty token?
if (token.empty()) {
throw std::runtime_error("empty option token");
}
// Invalid token?
if (!(token.front() == '-' || token.front() == '/')) {
throw std::runtime_error(
Utils::formatText("option token \"%1\" is invalid", {token}));
}
// Duplicated token?
if (std::any_of(options.begin(), options.end(), [&token](const Option &opt) {
for (const auto &item : opt.d_func()->tokens)
if (item == token)
return true;
const auto &tokens = opt.d_func()->tokens;
return std::any_of(tokens.begin(), tokens.end(),
[&token](const std::string &cur) {
return cur == token; //
});
})) {
throw std::runtime_error(
Utils::formatText("option token \"%1\" duplicated", {token}));
}
}
// Global and exclusive option?
if (!exclusiveGroup.empty() && opt.isGlobal()) {
throw std::runtime_error(Utils::formatText(
"global option \"%s\" cannot be in any exclusive group", {opt.token()}));
}
switch (opt.priorLevel()) {
case Option::AutoSetWhenNoSymbols: {
// Auto-option but required?
if (d->required) {
throw std::runtime_error(
Utils::formatText("auto-option \"%s\" cannot be required", {opt.token()}));
}
// Auto-option with argument?
if (!d->arguments.empty()) {
throw std::runtime_error(Utils::formatText(
"auto-option \"%s\" cannot have any argument", {opt.token()}));
}
break;
}
case Option::ExclusiveToOptions:
case Option::ExclusiveToAll: {
// Multiple exclusively prior option?
if (opt.isRequired() &&
std::any_of(options.begin(), options.end(), [](const Option &opt) {
return opt.priorLevel() >= Option::ExclusiveToOptions && opt.isRequired();
})) {
throw std::runtime_error("at most one exclusively prior and required option");
}
break;
}
default:
break;
}
// Inconsistent exclusive group?
if (!exclusiveGroup.empty()) {
for (size_t i = 0; i < options.size(); ++i) {
if (optionGroupNames[i] == exclusiveGroup &&
options[i].isRequired() != opt.isRequired()) {
throw std::runtime_error(
Utils::formatText(R"(option "%1" is %2, but exclusive group "3" isn't)",
{
opt.token(),
opt.isRequired() ? "required" : "required",
exclusiveGroup,
}));
}
}
}
}
#endif
Command::Command() : Command({}, {}) {
}
Command::Command(const std::string &name, const std::string &desc,
const std::vector<Argument> &args)
: ArgumentHolder(new CommandPrivate(name, desc)) {
if (!args.empty())
addArguments(args);
}
std::string Command::helpText(Symbol::HelpPosition pos, int displayOptions, void *extra) const {
Q_D2(Command);
if (auto ss = ArgumentHolder::helpText(pos, displayOptions, extra); !ss.empty()) {
return ss;
}
switch (pos) {
case HP_Usage: {
// `extra` should be null or a 3 pointer array
auto a = reinterpret_cast<void **>(extra);
// all options
const auto *globalOptions =
a ? reinterpret_cast<std::vector<Option> *>(a[0]) : nullptr;
auto options = d->options;
if (globalOptions) {
// options.insert(options.begin(), globalOptions->begin(),
// globalOptions->end());
options = OptionPrivate::reorderOptions(options, *globalOptions);
}
StringList groupNames(globalOptions ? globalOptions->size() : 0);
// groupNames.insert(groupNames.end(), d->optionGroupNames.begin(),
// d->optionGroupNames.end());
groupNames = Utils::concatVector(groupNames, d->optionGroupNames);
auto textProvider = a ? reinterpret_cast<Parser::TextProvider>(a[1]) : nullptr;
if (!textProvider) {
textProvider = Parser::defaultTextProvider();
}
std::string ss;
// write command name
ss += d->name;
// Build exclusive option group indexes
// group name -> option subscripts (vector<int> *)
GenericMap exclusiveGroupIndexes = [](const CommandPrivate *d) {
GenericMap res;
for (int i = 0; i < d->optionGroupNames.size(); ++i) {
const auto &group = d->optionGroupNames[i];
if (group.empty())
continue;
auto it = res.find(group);
if (it != res.end()) {
it->second.il->push_back(i);
continue;
}
res[group] = ele(new IntList({i}));
}
return res;
}(d);
auto addArgumentsHelp = [&](bool front) {
if (bool((displayOptions & Parser::ShowOptionsBehindArguments)) != front &&
!d->arguments.empty()) {
ss += " " + displayedArguments(displayOptions);
}
};
std::vector<int> visitedOptions(options.size()); // pointer -> none
int visitedCount = 0;
auto addVisited = [&visitedOptions, &visitedCount](int idx) {
if (visitedOptions[idx])
return;
visitedOptions[idx] = 1;
visitedCount++;
};
auto addExclusiveOptions = [&](int optIdx, bool required) {
const auto &opt = options[optIdx];
const auto &groupName = groupNames[optIdx];
do {
if (groupName.empty())
break;
const auto &optionIndexes =
*exclusiveGroupIndexes.find(groupName)->second.il;
if (optionIndexes.size() <= 1)
break;
// Add exclusive groups
if (required)
ss += "(";
StringList exclusiveOptions;
exclusiveOptions.reserve(optionIndexes.size());
for (const auto &idx : optionIndexes) {
const auto &curOpt = options[idx];
exclusiveOptions.push_back(
curOpt.helpText(Symbol::HP_Usage, displayOptions));
addVisited(idx);
}
ss += Utils::join(exclusiveOptions, " | ");
if (required)
ss += ")";
return;
} while (false);
// Add single
ss += opt.helpText(Symbol::HP_Usage, displayOptions);
addVisited(optIdx);
};
auto addOptionsHelp = [&](bool required) {
for (int i = 0; i < options.size(); ++i) {
const auto &opt = options[i];
if (opt.isRequired() != required || visitedOptions[i]) {
continue;
}
ss += " ";
if (!required)
ss += "[";
addExclusiveOptions(i, required);
if (!required)
ss += "]";
}
};
int frontOptionInsertionPos = ss.size();
// write forward arguments
addArgumentsHelp(true);
// write required options
if (!(displayOptions & Parser::DontShowRequiredOptionsOnUsage))
addOptionsHelp(true);
// write optional options
if ((displayOptions & Parser::ShowOptionalOptionsOnUsage) &&
visitedCount < options.size())
addOptionsHelp(false);
// write backward arguments
addArgumentsHelp(false);
if (!d->commands.empty()) {
ss += " [" + textProvider(Strings::Token, Strings::OptionalCommands) + "]";
}
if (visitedCount < options.size() || !d->commands.empty()) {
std::string optionHint =
" [" + textProvider(Strings::Token, Strings::OptionalOptions) + "]";
if ((displayOptions & Parser::ShowOptionsHintFront)) {
ss = ss.substr(0, frontOptionInsertionPos) + optionHint +
ss.substr(frontOptionInsertionPos);
} else {
ss += optionHint;
}
}
// release indexes
for (const auto &pair : std::as_const(exclusiveGroupIndexes)) {
delete pair.second.il;
}
return ss;
}
case HP_ErrorText:
case HP_FirstColumn: {
return d->name;
}
case HP_SecondColumn: {
return d->desc;
}
}
return {};
}
std::string Command::name() const {
Q_D2(Command);
return d->name;
}
void Command::setName(const std::string &name) {
Q_D(Command);
d->name = name;
}
void Command::addOptions(const std::vector<Option> &options, const std::string &group) {
Q_D(Command);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (const auto &opt : options) {
d->checkAddedOption(opt, group);
d->options.push_back(opt);
d->optionGroupNames.push_back(group);
}
#else
// d->options.insert(d->options.end(), options.begin(), options.end());
// d->optionGroupNames.insert(d->optionGroupNames.end(), options.size(), group);
d->options = Utils::concatVector(d->options, options);
d->optionGroupNames = Utils::concatVector(d->optionGroupNames, {options.size(), group});
#endif
}
int Command::commandCount() const {
Q_D2(Command);
return int(d->commands.size());
}
Command Command::command(int index) const {
Q_D2(Command);
return d->commands[index];
}
void Command::addCommands(const std::vector<Command> &commands) {
Q_D(Command);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (const auto &cmd : commands) {
d->checkAddedCommand(cmd);
d->commands.push_back(cmd);
}
#else
// d->commands.insert(d->commands.end(), commands.begin(), commands.end());
d->commands = Utils::concatVector(d->commands, commands);
#endif
}
std::string Command::detailedDescription() const {
Q_D2(Command);
return d->detailedDescription;
}
void Command::setDetailedDescription(const std::string &detailedDescription) {
Q_D(Command);
d->detailedDescription = detailedDescription;
}
Command::Handler Command::handler() const {
Q_D2(Command);
return d->handler;
}
void Command::setHandler(const Handler &handler) {
Q_D(Command);
d->handler = handler;
}
CommandCatalogue Command::catalogue() const {
Q_D2(Command);
return d->catalogue;
}
void Command::setCatalogue(const CommandCatalogue &catalogue) {
Q_D(Command);
d->catalogue = catalogue;
}
std::string Command::versionString() const {
Q_D2(Command);
return d->version;
}
void Command::addVersionOption(const std::string &version, const StringList &tokens,
const std::string &desc) {
Q_D(Command);
d->version = version;
Option versionOption(Option::Version, tokens, desc);
versionOption.setPriorLevel(Option::IgnoreMissingSymbols);
addOption(versionOption);
}
void Command::addHelpOption(bool showHelpIfNoArg, bool global, const StringList &tokens,
const std::string &desc) {
Q_D(Command);
Option helpOption(Option::Help, tokens, desc);
helpOption.setPriorLevel(showHelpIfNoArg ? Option::AutoSetWhenNoSymbols
: Option::IgnoreMissingSymbols);
helpOption.setGlobal(global);
addOption(helpOption);
}
HelpLayout Command::helpLayout() const {
Q_D2(Command);
return d->helpLayout;
}
void Command::setHelpLayout(const HelpLayout &helpLayout) {
Q_D(Command);
d->helpLayout = helpLayout;
}
}
@@ -0,0 +1,53 @@
#ifndef COMMAND_P_H
#define COMMAND_P_H
#include "argument_p.h"
#include "command.h"
#include "map_p.h"
namespace SysCmdLine {
class CommandCataloguePrivate : public SharedBasePrivate {
public:
SharedBasePrivate *clone() const;
StringListMapWrapper arg;
StringListMapWrapper opt;
StringListMapWrapper cmd;
StringList arguments;
StringList options;
StringList commands;
};
class CommandPrivate : public ArgumentHolderPrivate {
public:
CommandPrivate(std::string name, const std::string &desc);
SharedBasePrivate *clone() const override;
public:
std::string name;
std::vector<Option> options;
std::vector<std::string> optionGroupNames;
std::vector<Command> commands;
std::string version;
std::string detailedDescription;
CommandCatalogue catalogue;
Command::Handler handler;
HelpLayout helpLayout;
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void checkAddedCommand(const Command &cmd) const;
void checkAddedOption(const Option &opt, const std::string &exclusiveGroup) const;
#endif
};
}
#endif // COMMAND_P_H
@@ -0,0 +1,161 @@
#include "helplayout.h"
#include "helplayout_p.h"
#include <algorithm>
#include "system.h"
#include "parser_p.h"
#include "utils_p.h"
namespace SysCmdLine {
static inline void printLast(bool hasNext) {
if (hasNext)
u8info("\n");
}
static void defaultTextsPrinter(MessageType messageType, bool highlight,
const HelpLayout::Context &ctx) {
if (ctx.text->lines.empty())
return;
if (ctx.text->title.empty()) {
// Content
u8debug(messageType, highlight, "%s\n", ctx.text->lines.data());
} else {
// Title
u8debug(messageType, highlight, "%s:\n", ctx.text->title.data());
// Content
auto lines = Utils::split(ctx.text->lines, "\n");
for (const auto &line : std::as_const(lines))
u8debug(messageType, highlight, "%s%s\n", ctx.parser->d_func()->indent().data(),
line.data());
}
printLast(ctx.hasNext);
}
static void defaultInfoPrinter(const HelpLayout::Context &ctx) {
defaultTextsPrinter(MT_Debug, false, ctx);
}
static void defaultWarnPrinter(const HelpLayout::Context &ctx) {
defaultTextsPrinter(MT_Warning, false, ctx);
}
static void defaultErrorPrinter(const HelpLayout::Context &ctx) {
defaultTextsPrinter(MT_Critical, true, ctx);
}
static void defaultListPrinter(const HelpLayout::Context &ctx) {
if (ctx.list->firstColumn.empty())
return;
// Title
u8info("%s:\n", ctx.text->title.data());
const auto &list = ctx.list;
const auto &parserData = ctx.parser->d_func();
int widest = ctx.firstColumnLength;
if (widest == 0) {
for (const auto &item : list->firstColumn)
widest = std::max(widest, int(item.size()));
}
std::vector<std::string> res;
for (size_t i = 0; i < list->firstColumn.size(); ++i) {
const auto &first = list->firstColumn[i];
const auto &second = list->secondColumn[i];
auto lines = Utils::split(second, "\n");
if (lines.empty())
lines.emplace_back();
{
std::string ss;
ss += parserData->indent();
ss += first;
ss += std::string(widest - first.size(), ' ');
ss += parserData->spacing();
ss += lines.front();
res.push_back(ss);
}
for (size_t j = 1; j < lines.size(); ++j) {
std::string ss;
ss += parserData->indent();
ss += std::string(widest, ' ');
ss += parserData->spacing();
ss += std::as_const(lines)[j];
res.push_back(ss);
}
}
for (const auto &line : std::as_const(res)) {
u8info("%s\n", line.data());
}
printLast(ctx.hasNext);
}
HelpLayout::HelpLayout() : SharedBase(new HelpLayoutPrivate()) {
}
void HelpLayout::addHelpTextItem(HelpTextItem type, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::HelpText, type, out ? out : defaultInfoPrinter, {}, {}});
}
void HelpLayout::addHelpListItem(HelpListItem type, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::HelpList, type, out ? out : defaultListPrinter, {}, {}});
}
void HelpLayout::addMessageItem(MessageItem type, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back({HelpLayoutPrivate::Message, type, out ? out : [](MessageItem item) {
switch (item) {
case MI_Warning:
return defaultWarnPrinter;
case MI_Critical:
return defaultErrorPrinter;
default:
break ;
}
return defaultInfoPrinter;
}(type), {}, {}});
}
void HelpLayout::addUserHelpTextItem(const Text &text, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::UserHelpText, 0, out ? out : defaultInfoPrinter, text, {}});
}
void HelpLayout::addUserHelpListItem(const List &list, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::UserHelpList, 0, out ? out : defaultListPrinter, {}, list});
}
void HelpLayout::addUserIntroItem(const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back({HelpLayoutPrivate::UserIntro, 0, out, {}, {}});
}
HelpLayout HelpLayout::defaultHelpLayout() {
static HelpLayout instance = []() {
HelpLayout res;
res.addHelpTextItem(HT_Prologue);
res.addMessageItem(MI_Information);
res.addMessageItem(MI_Warning);
res.addMessageItem(MI_Critical);
res.addHelpTextItem(HT_Description);
res.addHelpTextItem(HT_Usage);
res.addHelpListItem(HL_Arguments);
res.addHelpListItem(HL_Options);
res.addHelpListItem(HL_Commands);
res.addHelpTextItem(HT_Epilogue);
return res;
}();
return instance;
}
}
@@ -0,0 +1,41 @@
#ifndef HELPLAYOUT_P_H
#define HELPLAYOUT_P_H
#include <utility>
#include "sharedbase_p.h"
#include "helplayout.h"
namespace SysCmdLine {
class HelpLayoutPrivate : public SharedBasePrivate {
public:
HelpLayoutPrivate *clone() const {
return new HelpLayoutPrivate(*this);
}
enum ItemType {
HelpText,
HelpList,
Message,
UserHelpText,
UserHelpList,
UserIntro,
};
struct ItemData {
ItemType itemType;
int index;
HelpLayout::Output out;
// Maybe
HelpLayout::Text text;
HelpLayout::List list;
};
std::vector<ItemData> itemDataList;
};
}
#endif // HELPLAYOUT_P_H
@@ -0,0 +1,114 @@
#ifndef MAP_P_H
#define MAP_P_H
#include <map>
#include <string>
#include <vector>
namespace SysCmdLine {
using StringList = std::vector<std::string>;
using IntList = std::vector<int>;
union Ele {
int i;
size_t s;
IntList *il;
StringList *sl;
void *p;
};
inline Ele ele(int i) noexcept {
#if __cplusplus >= 202002L
return Ele{.i = i};
#else
Ele res;
res.i = i;
return res;
#endif
}
inline Ele ele(size_t s) noexcept {
#if __cplusplus >= 202002L
return Ele{.s = s};
#else
Ele res;
res.s = s;
return res;
#endif
}
inline Ele ele(IntList *il) noexcept {
#if __cplusplus >= 202002L
return Ele{.il = il};
#else
Ele res;
res.il = il;
return res;
#endif
}
inline Ele ele(StringList *sl) noexcept {
#if __cplusplus >= 202002L
return Ele{.sl = sl};
#else
Ele res;
res.sl = sl;
return res;
#endif
}
inline Ele ele(void *p) noexcept {
#if __cplusplus >= 202002L
return Ele{.p = p};
#else
Ele res;
res.p = p;
return res;
#endif
}
// Avoiding template specialization greatly helps to reduce the binary size,
// so we only use this map in the library implementation.
using GenericMap = std::map<std::string, Ele>;
struct StringListMapWrapper {
StringListMapWrapper() = default;
StringListMapWrapper(const StringListMapWrapper &other) {
for (const auto &pair : other.data) {
data[pair.first] = ele(new StringList(*pair.second.sl));
}
}
~StringListMapWrapper() {
for (const auto &pair : std::as_const(data)) {
delete pair.second.sl;
}
}
GenericMap data;
};
struct IntListMapWrapper {
IntListMapWrapper() = default;
IntListMapWrapper(const IntListMapWrapper &other) {
for (const auto &pair : other.data) {
data[pair.first] = ele(new IntList(*pair.second.il));
}
}
~IntListMapWrapper() {
for (const auto &pair : std::as_const(data)) {
delete pair.second.il;
}
}
GenericMap data;
};
}
#endif // MAP_P_H
@@ -0,0 +1,214 @@
#include "option.h"
#include "option_p.h"
#include "utils_p.h"
#include "strings.h"
#include "parser.h"
namespace SysCmdLine {
static std::vector<std::string> defaultTokensForRole(Option::Role role) {
std::vector<std::string> res;
switch (role) {
case Option::Help:
res = {"-h", "--help"};
break;
case Option::Version:
res = {"-v", "--version"};
break;
case Option::Verbose:
res = {"-V", "--verbose"};
break;
case Option::Debug:
res = {"-d", "--debug"};
break;
default:
break;
}
return res;
}
OptionPrivate::OptionPrivate(Option::Role role, const std::vector<std::string> &tokens,
const std::string &desc, bool required)
: ArgumentHolderPrivate(Symbol::ST_Option, desc), role(role), tokens(tokens),
required(required), shortMatchRule(Option::NoShortMatch), priorLevel(Option::NoPrior),
global(false), maxOccurrence(1) {
}
SymbolPrivate *OptionPrivate::clone() const {
return new OptionPrivate(*this);
}
std::vector<Option> OptionPrivate::reorderOptions(const std::vector<Option> &options,
const std::vector<Option> &globalOptions) {
std::vector<Option> res;
std::vector<Option> roles;
for (const auto &opt : options) {
if (opt.role() == Option::NoRole) {
res.push_back(opt);
continue;
}
roles.push_back(opt);
}
for (const auto &opt : globalOptions) {
if (opt.role() == Option::NoRole) {
res.push_back(opt);
continue;
}
roles.push_back(opt);
}
for (const auto &opt : std::as_const(roles)) {
res.push_back(opt);
}
return res;
}
// template <typename... Args>
// Option(const char *firstToken, Args &&...args, const std::string &desc)
// : Option(std::vector<std::string>(), desc) {
// std::vector<std::string> tokens;
// tokens.emplace_back(firstToken);
// (tokens.emplace_back(std::forward<Args>(args)), ...);
// setTokens(tokens);
// }
Option::Option() : Option(NoRole, {}) {
}
Option::Option(Role role, const std::vector<std::string> &tokens, const std::string &desc)
: ArgumentHolder(new OptionPrivate(
role, tokens.empty() ? defaultTokensForRole(role) : tokens, desc, false)) {
}
Option::Option(const std::string &token, const std::string &desc, const Argument &arg,
bool required)
: Option(std::vector<std::string>{token}, desc, arg, required) {
}
Option::Option(const std::vector<std::string> &tokens, const std::string &desc,
const Argument &arg, bool required)
: ArgumentHolder(new OptionPrivate(NoRole, tokens, desc, required)) {
if (!arg.d_func()->name.empty())
addArgument(arg);
}
std::string Option::helpText(Symbol::HelpPosition pos, int displayOptions, void *extra) const {
Q_D2(Option);
if (auto ss = ArgumentHolder::helpText(pos, displayOptions, extra); !ss.empty()) {
return ss;
}
switch (pos) {
case Symbol::HP_Usage: {
std::string appendix;
if (!d->arguments.empty()) {
appendix = " " + displayedArguments(displayOptions);
}
return d->tokens.front() + appendix;
}
case Symbol::HP_ErrorText: {
return d->tokens.front();
}
case Symbol::HP_FirstColumn: {
std::string appendix;
if (!d->arguments.empty()) {
appendix = " " + displayedArguments(displayOptions);
}
return Utils::join(d->tokens, ", ") + appendix;
}
case Symbol::HP_SecondColumn: {
auto textProvider = reinterpret_cast<Parser::TextProvider>(extra);
if (!textProvider) {
textProvider = Parser::defaultTextProvider();
}
std::string appendix;
std::string desc = d->desc;
if (desc.empty() && d->role != NoRole) {
desc = textProvider(Strings::OptionRole,
static_cast<Strings::OptionRoleText>(d->role));
}
// Required
if (d->required && (displayOptions & Parser::ShowOptionIsRequired)) {
appendix += " [" + textProvider(Strings::Title, Strings::Required) + "]";
}
return desc + appendix;
}
}
return {};
}
Option::Role Option::role() const {
Q_D2(Option);
return d->role;
}
void Option::setRole(Role role) {
Q_D(Option);
d->role = role;
}
const std::vector<std::string> &Option::tokens() const {
Q_D2(Option);
return d->tokens;
}
void Option::setTokens(const std::vector<std::string> &tokens) {
Q_D(Option);
d->tokens = tokens;
}
bool Option::isRequired() const {
Q_D2(Option);
return d->required;
}
void Option::setRequired(bool required) {
Q_D(Option);
d->required = required;
}
Option::ShortMatchRule Option::shortMatchRule() const {
Q_D2(Option);
return d->shortMatchRule;
}
void Option::setShortMatchRule(ShortMatchRule shortMatchRule) {
Q_D(Option);
d->shortMatchRule = shortMatchRule;
}
Option::PriorLevel Option::priorLevel() const {
Q_D2(Option);
return d->priorLevel;
}
void Option::setPriorLevel(PriorLevel priorLevel) {
Q_D(Option);
d->priorLevel = priorLevel;
}
bool Option::isGlobal() const {
Q_D2(Option);
return d->global;
}
void Option::setGlobal(bool on) {
Q_D(Option);
d->global = on;
}
int Option::maxOccurrence() const {
Q_D2(Option);
return d->maxOccurrence;
}
void Option::setMaxOccurrence(int max) {
Q_D(Option);
d->maxOccurrence = max;
}
}
@@ -0,0 +1,31 @@
#ifndef OPTION_P_H
#define OPTION_P_H
#include "argument_p.h"
#include "option.h"
namespace SysCmdLine {
class OptionPrivate : public ArgumentHolderPrivate {
public:
OptionPrivate(Option::Role role, const std::vector<std::string> &tokens,
const std::string &desc, bool required);
SymbolPrivate *clone() const override;
public:
Option::Role role;
std::vector<std::string> tokens;
bool required;
Option::ShortMatchRule shortMatchRule;
Option::PriorLevel priorLevel;
bool global;
int maxOccurrence;
static std::vector<Option> reorderOptions(const std::vector<Option> &options,
const std::vector<Option> &globalOptions);
};
}
#endif // OPTION_P_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
#ifndef PARSER_P_H
#define PARSER_P_H
#include "sharedbase_p.h"
#include "parser.h"
namespace SysCmdLine {
class ParserPrivate : public SharedBasePrivate {
public:
ParserPrivate();
ParserPrivate *clone() const;
Command rootCommand;
std::string prologue, epilogue;
int displayOptions;
int sizeConfig[3] = {
4,
4,
80,
};
Parser::TextProvider textProvider;
inline std::string indent() const {
return std::string(sizeConfig[Parser::ST_Indent], ' ');
}
inline std::string spacing() const {
return std::string(sizeConfig[Parser::ST_Spacing], ' ');
}
};
}
#endif // PARSER_P_H
@@ -0,0 +1,725 @@
#include "parseresult.h"
#include "parseresult_p.h"
#include <algorithm>
#include <stdexcept>
#include "utils_p.h"
#include "strings.h"
#include "system.h"
#include "parser_p.h"
#include "option_p.h"
#include "helplayout_p.h"
#include "command_p.h"
namespace SysCmdLine {
const OptionData &OptionData::sharedNull() {
static OptionData _data;
return _data;
}
static const std::vector<Value> &sharedEmptyValues() {
static std::vector<Value> _data;
return _data;
}
OptionResult::OptionResult() : data(&OptionData::sharedNull()) {
}
Option OptionResult::option() const {
auto &v = *reinterpret_cast<const OptionData *>(data);
return v.option ? *v.option : Option();
}
int OptionResult::indexOf(const std::string &name) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
auto it = v.argNameIndexes.find(name);
if (it == v.argNameIndexes.end())
return -1;
return it->second.i;
}
int OptionResult::count() const {
auto &v = *reinterpret_cast<const OptionData *>(data);
return v.count;
}
std::vector<Value> OptionResult::allValues(int index) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
if (index < 0 || index >= v.argSize)
return {};
std::vector<Value> allValues;
for (int i = 0; i < v.count; ++i) {
const auto &values = v.argResult[i][index];
// allValues.insert(allValues.end(), values.begin(), values.end());
allValues = Utils::concatVector(allValues, values);
}
return allValues;
}
const std::vector<Value> &OptionResult::values(int index, int n) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
if (index < 0 || index >= v.argSize)
return sharedEmptyValues();
if (n < 0 || n >= v.count)
return sharedEmptyValues();
return v.argResult[n][index];
}
Value OptionResult::value(int index, int n) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
if (index < 0 || index >= v.argSize)
return {};
if (n < 0 || n >= v.count)
return v.option->d_func()->arguments[index].defaultValue();
const auto &args = v.argResult[n][index];
return args.empty() ? Value() : args.front();
}
std::string ParseResultPrivate::correctionText() const {
std::vector<std::string> expectedValues;
switch (error) {
case ParseResult::UnknownOption: {
for (int i = 0; i < core.allOptionsSize; ++i) {
const auto &opt = *core.allOptionsResult[i].option;
for (const auto &token : opt.d_func()->tokens) {
expectedValues.push_back(token);
}
}
break;
}
case ParseResult::InvalidArgumentValue: {
auto d = errorArgument->d_func();
for (const auto &item : d->expectedValues) {
expectedValues.push_back(item.toString());
}
break;
}
case ParseResult::UnknownCommand: {
for (const auto &cmd : command->d_func()->commands) {
expectedValues.push_back(cmd.name());
}
break;
}
default:
return {};
}
auto input = errorPlaceholders[0];
auto suggestions = Utils::calcClosestTexts(expectedValues, input, int(input.size()) / 2);
if (suggestions.empty())
return {};
std::string ss;
const auto &parserData = parser.d_func();
ss += Utils::formatText(
parserData->textProvider(Strings::Information, Strings::MatchCommand), {input});
for (const auto &item : std::as_const(suggestions)) {
ss += "\n" + parserData->indent() + item;
}
return ss;
}
std::vector<Option> ParseResultPrivate::globalOptions() const {
std::vector<Option> res;
res.reserve(core.globalOptionsSize);
for (int i = 0; i < core.globalOptionsSize; ++i) {
res.push_back(*core.allOptionsResult[i].option);
}
return res;
}
void ParseResultPrivate::showMessage(const std::string &info, const std::string &warn,
const std::string &err, bool isMsg) const {
// Make it as a POD structure
struct Lists {
HelpLayout::List *data;
int size;
};
// Avoid using `std::function` to reduce binary size
const auto &getLists = [](int displayOptions, const GenericMap &catalog,
const StringList &catalogNames, // catalog
const GenericMap &symbolIndexes, // name -> index
int symbolCount, const Symbol *(*getter)(int, const void *),
const void *user, // get symbol from index
std::string (*getName)(const Symbol *), // get name of symbol
int *maxWidth, void *extra,
const std::string &defaultTitle) -> Lists {
Lists res;
int index = 0;
res.data = new HelpLayout::List[catalogNames.size() + 1];
// symbol indexes
std::vector<int> visitedIndexes(symbolCount);
// catalogues
for (const auto &catalogName : catalogNames) {
auto &list = res.data[index];
list.title = catalogName;
auto &symbolNames = *catalog.find(catalogName)->second.sl;
bool empty = true;
for (const auto &name : symbolNames) {
auto it = symbolIndexes.find(name);
if (it == symbolIndexes.end())
continue;
auto idx = it->second.i;
const auto &sym = getter(idx, user);
auto first = sym->helpText(Symbol::HP_FirstColumn, displayOptions, extra);
auto second = sym->helpText(Symbol::HP_SecondColumn, displayOptions, extra);
*maxWidth = std::max(int(first.size()), *maxWidth);
list.firstColumn.emplace_back(std::move(first));
list.secondColumn.emplace_back(std::move(second));
visitedIndexes[idx] = 1;
empty = false;
}
if (!empty)
index++; // index inc
}
// rest
{
auto &list = res.data[index];
list.title = defaultTitle;
bool empty = true;
for (int i = 0; i < symbolCount; ++i) {
if (visitedIndexes[i])
continue;
const auto &sym = getter(i, user);
auto first = sym->helpText(Symbol::HP_FirstColumn, displayOptions, extra);
auto second = sym->helpText(Symbol::HP_SecondColumn, displayOptions, extra);
*maxWidth = std::max(int(first.size()), *maxWidth);
list.firstColumn.emplace_back(std::move(first));
list.secondColumn.emplace_back(std::move(second));
empty = false;
}
if (!empty)
index++; // index inc
}
res.size = index;
return res;
};
const auto &d = command->d_func();
const auto &parserData = parser.d_func();
const auto &catalogueData = d->catalogue.d_func();
const auto displayOptions = parserData->displayOptions;
bool noHelp = isMsg && (displayOptions & Parser::DontShowHelpOnError);
bool noIntro = isMsg && (displayOptions & Parser::DontShowIntroOnError);
int maxWidth = 0;
auto reorderedOptions = OptionPrivate::reorderOptions(d->options, globalOptions());
// Alloc
Lists argLists =
noHelp ? Lists{nullptr, 0}
: getLists(
displayOptions, catalogueData->arg.data, catalogueData->arguments,
core.argNameIndexes, int(d->arguments.size()),
[](int i, const void *user) -> const Symbol * {
return &reinterpret_cast<decltype(d)>(user)->arguments[i]; //
},
d,
[](const Symbol *s) {
return static_cast<const Argument *>(s)->name(); //
},
&maxWidth, reinterpret_cast<void *>(parserData->textProvider),
parserData->textProvider(Strings::Title, Strings::Arguments));
Lists optLists =
noHelp
? Lists{nullptr, 0}
: getLists(
displayOptions, catalogueData->opt.data, catalogueData->options,
core.allOptionTokenIndexes, int(core.allOptionsSize),
[](int i, const void *user) -> const Symbol * {
return &(*reinterpret_cast<const decltype(reorderedOptions) *>(user))[i];
},
&reorderedOptions,
[](const Symbol *s) {
return static_cast<const Option *>(s)->token(); //
},
&maxWidth, reinterpret_cast<void *>(parserData->textProvider),
parserData->textProvider(Strings::Title, Strings::Options));
Lists cmdLists = noHelp
? Lists{nullptr, 0}
: getLists(
displayOptions, catalogueData->cmd.data, catalogueData->commands,
core.cmdNameIndexes, int(d->commands.size()),
[](int i, const void *user) -> const Symbol * {
return &reinterpret_cast<decltype(d)>(user)->commands[i]; //
},
d,
[](const Symbol *s) {
return static_cast<const Command *>(s)->name(); //
},
&maxWidth, reinterpret_cast<void *>(parserData->textProvider),
parserData->textProvider(Strings::Title, Strings::Commands));
// Get valid help layout
const HelpLayoutPrivate *helpLayoutData = nullptr;
{
int cmdCnt = stack.size() + 1;
auto cmds = new const Command *[cmdCnt]; // alloc
auto cmd = &parserData->rootCommand;
cmds[0] = cmd;
int cmdIdx = 0;
for (const auto &idx : std::as_const(stack)) {
cmd = &cmd->d_func()->commands[idx];
cmds[++cmdIdx] = cmd;
}
for (int i = cmdCnt - 1; i >= 0; --i) {
auto curHelpLayoutData = cmds[i]->d_func()->helpLayout.d_func();
if (!curHelpLayoutData->itemDataList.empty()) {
helpLayoutData = curHelpLayoutData;
break;
}
}
delete[] cmds; // free
if (!helpLayoutData) {
static HelpLayout defaultHelpLayout = HelpLayout::defaultHelpLayout();
helpLayoutData = defaultHelpLayout.d_func();
}
}
if ((displayOptions & Parser::AlignAllCatalogues)) {
for (const auto &helpItem : helpLayoutData->itemDataList) {
if (helpItem.itemType != HelpLayoutPrivate::UserHelpList) {
continue;
}
for (const auto &item : helpItem.list.firstColumn) {
maxWidth = std::max(int(item.size()), maxWidth);
}
}
} else {
maxWidth = 0;
}
const auto &cmdDesc = d->detailedDescription.empty() ? d->desc : d->detailedDescription;
// Get last
int last = int(helpLayoutData->itemDataList.size()) - 1;
for (int i = last; i >= 0; --i) {
const auto &item = helpLayoutData->itemDataList[i];
bool empty = true;
switch (item.itemType) {
case HelpLayoutPrivate::HelpText: {
switch (static_cast<HelpLayout::HelpTextItem>(item.index)) {
case HelpLayout::HT_Prologue: {
if (noIntro)
break;
empty = parserData->prologue.empty();
break;
}
case HelpLayout::HT_Epilogue: {
if (noIntro)
break;
empty = parserData->epilogue.empty();
break;
}
case HelpLayout::HT_Description: {
if (noHelp)
break;
empty = cmdDesc.empty();
break;
}
case HelpLayout::HT_Usage: {
if (noHelp)
break;
empty = false;
break;
}
}
break;
}
case HelpLayoutPrivate::HelpList: {
if (noHelp)
break;
switch (static_cast<HelpLayout::HelpListItem>(item.index)) {
case HelpLayout::HL_Arguments: {
empty = argLists.size == 0;
break;
}
case HelpLayout::HL_Options: {
empty = optLists.size == 0;
break;
}
case HelpLayout::HL_Commands: {
empty = cmdLists.size == 0;
break;
}
}
break;
}
case HelpLayoutPrivate::Message: {
switch (static_cast<HelpLayout::MessageItem>(item.index)) {
case HelpLayout::MI_Information: {
empty = info.empty();
break;
}
case HelpLayout::MI_Warning: {
empty = warn.empty();
break;
}
case HelpLayout::MI_Critical: {
empty = err.empty();
break;
}
}
break;
}
case HelpLayoutPrivate::UserHelpText: {
if (noHelp)
break;
empty = item.text.lines.empty();
break;
}
case HelpLayoutPrivate::UserHelpList: {
if (noHelp)
break;
empty = item.list.firstColumn.empty();
break;
}
case HelpLayoutPrivate::UserIntro: {
if (noIntro)
break;
empty = false;
break;
}
}
if (!empty) {
last = i;
break;
}
}
// Output
for (int i = 0; i <= last; ++i) {
const auto &item = helpLayoutData->itemDataList[i];
HelpLayout::Context ctx;
ctx.parser = &parser;
bool hasNext = i < last;
ctx.hasNext = hasNext;
switch (item.itemType) {
case HelpLayoutPrivate::HelpText: {
HelpLayout::Text text;
ctx.text = &text;
switch (static_cast<HelpLayout::HelpTextItem>(item.index)) {
case HelpLayout::HT_Prologue: {
if (noIntro)
break;
text.lines = parserData->prologue;
break;
}
case HelpLayout::HT_Epilogue: {
if (noIntro)
break;
text.lines = parserData->epilogue;
break;
}
case HelpLayout::HT_Description: {
if (noHelp)
break;
text.title =
parserData->textProvider(Strings::Title, Strings::Description);
text.lines = cmdDesc;
break;
}
case HelpLayout::HT_Usage: {
if (noHelp)
break;
std::vector<Option> allOptions;
allOptions.reserve(core.globalOptionsSize);
for (int j = 0; j < core.globalOptionsSize; ++j) {
allOptions.emplace_back(*core.allOptionsResult[j].option);
}
{
// get parent names
auto p = &parserData->rootCommand;
for (int index : stack) {
text.lines = p->name() + " ";
p = &p->d_func()->commands[index];
}
}
void *a[2] = {
&allOptions,
reinterpret_cast<void *>(parserData->textProvider),
};
text.title = parserData->textProvider(Strings::Title, Strings::Usage);
text.lines += command->helpText(Symbol::HP_Usage, displayOptions, a);
break;
}
}
item.out(ctx);
break;
}
case HelpLayoutPrivate::HelpList: {
if (noHelp)
break;
const auto &listHasNext = [](int j, const Lists &lists) {
return j < lists.size - 1;
};
ctx.firstColumnLength = maxWidth;
switch (static_cast<HelpLayout::HelpListItem>(item.index)) {
case HelpLayout::HL_Arguments: {
for (int j = 0; j < argLists.size; ++j) {
ctx.hasNext = hasNext || listHasNext(j, argLists);
ctx.list = &argLists.data[j];
item.out(ctx);
}
break;
}
case HelpLayout::HL_Options: {
for (int j = 0; j < optLists.size; ++j) {
ctx.hasNext = hasNext || listHasNext(j, optLists);
ctx.list = &optLists.data[j];
item.out(ctx);
}
break;
}
case HelpLayout::HL_Commands: {
for (int j = 0; j < cmdLists.size; ++j) {
ctx.hasNext = hasNext || listHasNext(j, cmdLists);
ctx.list = &cmdLists.data[j];
item.out(ctx);
}
break;
}
}
break;
}
case HelpLayoutPrivate::Message: {
HelpLayout::Text text;
ctx.text = &text;
switch (static_cast<HelpLayout::MessageItem>(item.index)) {
case HelpLayout::MI_Information: {
text.lines = info;
break;
}
case HelpLayout::MI_Warning: {
text.lines = warn;
break;
}
case HelpLayout::MI_Critical: {
text.lines = err;
break;
}
}
item.out(ctx);
break;
}
case HelpLayoutPrivate::UserHelpText: {
if (noHelp)
break;
ctx.text = &item.text;
item.out(ctx);
break;
}
case HelpLayoutPrivate::UserHelpList: {
if (noHelp)
break;
ctx.list = &item.list;
ctx.firstColumnLength = maxWidth;
item.out(ctx);
break;
}
case HelpLayoutPrivate::UserIntro: {
if (noHelp)
break;
item.out(ctx);
break;
}
}
}
// Free
delete[] argLists.data;
delete[] optLists.data;
delete[] cmdLists.data;
}
ParseResult::ParseResult() : SharedBase(nullptr) {
}
Command ParseResult::rootCommand() const {
Q_D2(ParseResult);
return d->parser.rootCommand();
}
const std::vector<std::string> &ParseResult::arguments() const {
Q_D2(ParseResult);
return d->arguments;
}
int ParseResult::invoke(int errCode) const {
Q_D2(ParseResult);
if (d->error != NoError) {
showError();
return errCode;
}
return dispatch();
}
int ParseResult::dispatch() const {
Q_D2(ParseResult);
if (d->error != NoError) {
throw std::runtime_error("don't call dispatch when parsing fails");
}
const auto &cmdData = d->command->d_func();
const auto &handler = cmdData->handler;
// If version is empty, you should do something in the handler
if (d->roleSet[Option::Version] && !cmdData->version.empty()) {
u8info("%s\n", cmdData->version.data());
return 0;
}
if (d->roleSet[Option::Help]) {
showHelpText();
return 0;
}
if (!handler) {
throw std::runtime_error(
Utils::formatText(R"(command "%1" doesn't have a valid handler)", {
cmdData->name,
}));
}
return handler(*this);
}
ParseResult::Error ParseResult::error() const {
Q_D2(ParseResult);
return d->error;
}
std::string ParseResult::errorText() const {
Q_D2(ParseResult);
if (d->error == NoError)
return {};
return Utils::formatText(d->parser.d_func()->textProvider(Strings::ParseError, d->error),
d->errorPlaceholders);
}
std::string ParseResult::correctionText() const {
Q_D2(ParseResult);
return d->correctionText();
}
std::string ParseResult::cancellationToken() const {
Q_D2(ParseResult);
return d->cancellationToken;
}
Command ParseResult::command() const {
Q_D2(ParseResult);
return *d->command;
}
std::vector<Option> ParseResult::globalOptions() const {
Q_D2(ParseResult);
return d->globalOptions();
}
std::vector<int> ParseResult::commandIndexStack() const {
Q_D2(ParseResult);
return d->stack;
}
int ParseResult::indexOfArgument(const std::string &name) const {
Q_D2(ParseResult);
auto it = d->core.argNameIndexes.find(name);
if (it == d->core.argNameIndexes.end())
return -1;
return it->second.i;
}
int ParseResult::indexOfOption(const std::string &token) const {
Q_D2(ParseResult);
auto it = d->core.allOptionTokenIndexes.find(token);
if (it == d->core.allOptionTokenIndexes.end())
return -1;
return it->second.i;
}
void ParseResult::showError() const {
Q_D2(ParseResult);
if (d->error == NoError)
return;
const auto &parserData = d->parser.d_func();
const auto &displayOptions = parserData->displayOptions;
d->showMessage(
(displayOptions & Parser::SkipCorrection) ? std::string() : d->correctionText(), {},
parserData->textProvider(Strings::Title, Strings::Error) + ": " + errorText(), true);
}
void ParseResult::showHelpText() const {
Q_D2(ParseResult);
d->showMessage({}, {}, {});
}
void ParseResult::showMessage(const std::string &info, const std::string &warn,
const std::string &err) const {
Q_D2(ParseResult);
const auto &displayOptions = d->parser.d_func()->displayOptions;
d->showMessage(info, warn, err, true);
}
bool ParseResult::isRoleSet(Option::Role role) const {
Q_D2(ParseResult);
return d->roleSet[role];
}
const std::vector<Value> &ParseResult::values(int index) const {
Q_D2(ParseResult);
if (index < 0 || index >= d->core.argSize)
return sharedEmptyValues();
return d->core.argResult[index];
}
Value ParseResult::value(int index) const {
Q_D2(ParseResult);
if (index < 0 || index >= d->core.argSize)
return {};
const auto &args = d->core.argResult[index];
return args.empty() ? d->command->d_func()->arguments[index].defaultValue() : args.front();
}
OptionResult ParseResult::option(int index) const {
Q_D2(ParseResult);
if (index < 0 || index >= d->core.allOptionsSize)
return {};
return {&d->core.allOptionsResult[index]};
}
ParseResult::ParseResult(ParseResultPrivate *d) : SharedBase(d) {
}
}
@@ -0,0 +1,100 @@
#ifndef PARSERESULT_P_H
#define PARSERESULT_P_H
#include "sharedbase_p.h"
#include "parser.h"
#include "map_p.h"
namespace SysCmdLine {
struct ArgumentHolderData {
int optionalArgIndex;
int multiValueArgIndex;
GenericMap argNameIndexes; // name -> index of argument
int argSize; // equal to `argumentCount()`
ArgumentHolderData() : optionalArgIndex(-1), multiValueArgIndex(-1), argSize(0) {
}
};
struct OptionData : public ArgumentHolderData {
const Option *option; // MUST BE SET
std::vector<Value> **argResult; // first: occurrence, second: arg index
int count; // occurrence times
OptionData() : option(nullptr), argResult(nullptr), count(0) {
}
~OptionData() {
for (int i = 0; i < count; ++i) {
delete[] argResult[i];
}
delete[] argResult;
}
static const OptionData &sharedNull();
};
struct ParseResultData2 : public ArgumentHolderData {
std::vector<Value> *argResult; // arg result
OptionData *allOptionsResult; // option result
int allOptionsSize; // command option count + global option count
int globalOptionsSize; // global option count
GenericMap allOptionTokenIndexes; // token -> index of `allOptionsResult`
GenericMap cmdNameIndexes; // name -> index of command
~ParseResultData2() {
delete[] allOptionsResult;
delete[] argResult;
}
};
class ParseResultPrivate : public SharedBasePrivate {
public:
ParseResultPrivate()
: error(ParseResult::NoError), errorOption(nullptr), errorArgument(nullptr) {
}
ParseResultPrivate(const ParseResultPrivate &) = delete;
ParseResultPrivate &operator=(const ParseResultPrivate &) = delete;
// This is a read-only class, we can store the pointers pointing to
// its own data, and we don't need to implement the clone method
SharedBasePrivate *clone() const {
return nullptr;
}
// parse information
Parser parser;
std::vector<std::string> arguments;
// error related
ParseResult::Error error;
std::vector<std::string> errorPlaceholders;
const Option *errorOption;
const Argument *errorArgument;
std::string cancellationToken;
// success results
std::vector<int> stack;
const Command *command; // MUST BE SET
ParseResultData2 core;
bool roleSet[5] = {};
std::string correctionText() const;
std::vector<Option> globalOptions() const;
// isMsg: mainly to display message rather than help
void showMessage(const std::string &info, const std::string &catalogNames,
const std::string &symbolIndexes, bool isMsg = false) const;
};
}
#endif // PARSERESULT_P_H
@@ -0,0 +1,50 @@
#include "sharedbase.h"
#include "sharedbase_p.h"
namespace SysCmdLine {
// static int g_cnt;
SharedBasePrivate::SharedBasePrivate() : ref(1) {
// printf("construct %d\n", ++g_cnt);
}
SharedBasePrivate::~SharedBasePrivate() {
// printf("destruct %d\n", --g_cnt);
}
SharedBase::~SharedBase() {
ref_deref(d_ptr);
}
SharedBase::SharedBase(const SharedBase &other) : d_ptr(other.d_ptr) {
ref_ref(d_ptr);
}
SharedBase &SharedBase::operator=(const SharedBase &other) {
if (this == &other) {
return *this;
}
ref_assign(d_ptr, other.d_ptr);
return *this;
}
bool SharedBase::isDetached() const {
Q_D2(SharedBase);
return d && d->ref.load() == 1;
}
void SharedBase::detach() {
auto &d = d_ptr;
if (d->ref.load() != 1) {
auto x = d->clone();
if (d->ref.fetch_sub(1) == 1) // atomicity
delete d;
d = x;
}
}
SharedBase::SharedBase(SharedBasePrivate *d) : d_ptr(d) {
}
}
@@ -0,0 +1,51 @@
#ifndef SHAREDBASE_P_H
#define SHAREDBASE_P_H
#include <atomic>
namespace SysCmdLine {
class SharedBasePrivate {
public:
SharedBasePrivate();
virtual ~SharedBasePrivate();
virtual SharedBasePrivate *clone() const = 0;
// Declare basic copy constructor
SharedBasePrivate(const SharedBasePrivate &) : ref(0) {
}
SharedBasePrivate &operator=(const SharedBasePrivate &) = delete;
std::atomic_int ref;
};
inline void ref_ref(SharedBasePrivate *d) {
if (d)
d->ref.fetch_add(1);
}
inline void ref_deref(SharedBasePrivate *d) {
if (d && d->ref.fetch_sub(1) == 1)
delete d;
}
inline void ref_assign(SharedBasePrivate *&d, SharedBasePrivate *d2) {
if (d2)
d2->ref.fetch_add(1);
if (d && d->ref.fetch_sub(1) == 1)
delete d;
d = d2;
}
#define Q_D(T) \
detach(); \
T##Private *const d = reinterpret_cast<T##Private *>(d_ptr)
#define Q_D2(T) \
const T##Private *const d = reinterpret_cast<const T##Private *>(d_ptr)
}
#endif // SHAREDBASE_P_H
@@ -0,0 +1,65 @@
#include "symbol.h"
#include "symbol_p.h"
#include <utility>
namespace SysCmdLine {
SymbolPrivate::SymbolPrivate(Symbol::SymbolType type, std::string desc)
: type(type), desc(std::move(desc)) {
}
/*!
Returns the symbol type.
*/
Symbol::SymbolType Symbol::type() const {
Q_D2(Symbol);
return d->type;
}
/*!
Returns the symbol description.
*/
std::string Symbol::description() const {
Q_D2(Symbol);
return d->desc;
}
/*!
Sets the symbol description.
*/
void Symbol::setDescription(const std::string &desc) {
Q_D(Symbol);
d->desc = desc;
}
/*!
Returns the help provider.
*/
Symbol::HelpProvider Symbol::helpProvider() const {
Q_D2(Symbol);
return d->helpProvider;
}
/*!
Sets the help provider.
*/
void Symbol::setHelpProvider(const Symbol::HelpProvider &helpProvider) {
Q_D(Symbol);
d->helpProvider = helpProvider;
}
/*!
Returns the help text at given position with given display options.
*/
std::string Symbol::helpText(Symbol::HelpPosition pos, int displayOptions, void *extra) const {
Q_D2(Symbol);
if (d->helpProvider)
return d->helpProvider(this, pos, displayOptions, extra);
return {};
}
Symbol::Symbol(SymbolPrivate *d) : SharedBase(d) {
}
}
@@ -0,0 +1,23 @@
#ifndef SYMBOL_P_H
#define SYMBOL_P_H
#include <atomic>
#include "symbol.h"
#include "sharedbase_p.h"
namespace SysCmdLine {
class SymbolPrivate : public SharedBasePrivate {
public:
SymbolPrivate(Symbol::SymbolType type, std::string desc);
public:
Symbol::SymbolType type;
std::string desc;
Symbol::HelpProvider helpProvider;
};
}
#endif // SYMBOL_P_H
@@ -0,0 +1,520 @@
#include "system.h"
#include <algorithm>
#include <limits>
#include <mutex>
#include <cstring>
#include <cstdarg>
#ifdef _WIN32
# include <windows.h>
# include <shellapi.h>
# include "utils_p.h"
#else
# include <limits.h>
# include <sys/stat.h>
# include <utime.h>
# ifdef __APPLE__
# include <crt_externs.h>
# include <mach-o/dyld.h>
# else
# include <fstream>
# endif
# include <filesystem>
#endif
#ifdef _WIN32
# ifndef WC_ERR_INVALID_CHARS
# define WC_ERR_INVALID_CHARS 0x00000080
# endif
#endif
namespace SysCmdLine {
#ifdef _WIN32
static std::wstring winGetFullModuleFileName(HMODULE hModule) {
// Use stack buffer for the first try
wchar_t stackBuf[MAX_PATH + 1];
// Call
wchar_t *buf = stackBuf;
auto size = ::GetModuleFileNameW(nullptr, buf, MAX_PATH);
if (size == 0) {
return {};
}
if (size > MAX_PATH) {
// Re-alloc
buf = new wchar_t[size + 1]; // The return size doesn't contain the terminating 0
// Call
if (::GetModuleFileNameW(nullptr, buf, size) == 0) {
delete[] buf;
return {};
}
}
std::replace(buf, buf + size, L'\\', L'/');
// Return
std::wstring res(buf);
if (buf != stackBuf) {
delete[] buf;
}
return res;
}
#elif defined(__APPLE__)
static std::string macGetExecutablePath() {
// Use stack buffer for the first try
char stackBuf[PATH_MAX + 1];
// "_NSGetExecutablePath" will return "-1" if the buffer is not large enough
// and "*bufferSize" will be set to the size required.
// Call
unsigned int size = PATH_MAX + 1;
char *buf = stackBuf;
if (_NSGetExecutablePath(buf, &size) != 0) {
// Re-alloc
buf = new char[size]; // The return size contains the terminating 0
// Call
if (_NSGetExecutablePath(buf, &size) != 0) {
delete[] buf;
return {};
}
}
// Return
std::string res(buf);
if (buf != stackBuf) {
delete[] buf;
}
return res;
}
#endif
/*!
Returns the wide string converted from UTF-8 multi-byte string.
*/
std::string wideToUtf8(const std::wstring &s) {
#ifdef _WIN32
if (s.empty()) {
return {};
}
const auto utf16Length = static_cast<int>(s.size());
if (utf16Length >= (std::numeric_limits<int>::max)()) {
return {};
}
const int utf8Length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.data(),
utf16Length, nullptr, 0, nullptr, nullptr);
if (utf8Length <= 0) {
return {};
}
std::string utf8Str;
utf8Str.resize(utf8Length);
::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.data(), utf16Length, utf8Str.data(),
utf8Length, nullptr, nullptr);
return utf8Str;
#else
return std::filesystem::path(s).string();
#endif
}
/*!
Returns the UTF-8 multi-byte string converted from wide string.
*/
std::wstring utf8ToWide(const std::string &s) {
#ifdef _WIN32
if (s.empty()) {
return {};
}
const auto utf8Length = static_cast<int>(s.size());
if (utf8Length >= (std::numeric_limits<int>::max)()) {
return {};
}
const int utf16Length =
::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), utf8Length, nullptr, 0);
if (utf16Length <= 0) {
return {};
}
std::wstring utf16Str;
utf16Str.resize(utf16Length);
::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), utf8Length, utf16Str.data(),
utf16Length);
return utf16Str;
#else
return std::filesystem::path(s).wstring();
#endif
}
#ifdef _WIN32
/*!
Returns the UTF-8 multi-byte string converted from ANSI string.
*/
std::string ansiToUtf8(const std::string &s) {
if (s.empty()) {
return {};
}
const auto ansiLength = static_cast<int>(s.size());
if (ansiLength >= (std::numeric_limits<int>::max)()) {
return {};
}
const int utf16Length =
::MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, s.data(), ansiLength, nullptr, 0);
if (utf16Length <= 0) {
return {};
}
std::wstring utf16Str;
utf16Str.resize(utf16Length);
::MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, s.data(), ansiLength, utf16Str.data(),
utf16Length);
return wideToUtf8(utf16Str);
}
#endif
/*!
Returns the application file path, the path separator of which is <tt>/</tt>.
*/
std::string appPath() {
static const auto res = []() -> std::string {
#ifdef _WIN32
return wideToUtf8(winGetFullModuleFileName(nullptr));
#elif defined(__APPLE__)
return macGetExecutablePath();
#else
char buf[PATH_MAX];
if (!realpath("/proc/self/exe", buf)) {
return {};
}
return buf;
#endif
}();
return res;
}
/*!
Returns the application directory path, the path separator of which is <tt>/</tt>.
*/
std::string appDirectory() {
auto appDir = appPath();
auto slashIdx = appDir.find_last_of('/');
if (slashIdx != std::string::npos) {
appDir = appDir.substr(0, slashIdx);
}
return appDir;
}
/*!
Returns the application file name.
*/
std::string appFileName() {
auto appName = appPath();
auto slashIdx = appName.find_last_of('/');
if (slashIdx != std::string::npos) {
appName = appName.substr(slashIdx + 1);
}
return appName;
}
/*!
Returns the application name. On Windows, the file extension will be stripped.
*/
std::string appName() {
auto appName = appFileName();
#ifdef _WIN32
auto dotIdx = appName.find_last_of('.');
if (dotIdx != std::string::npos) {
std::string suffix = Utils::toLower(appName.substr(dotIdx + 1));
if (suffix == "exe") {
appName = appName.substr(0, dotIdx);
}
}
#endif
return appName;
}
/*!
Returns the command line argument list in UTF-8.
*/
std::vector<std::string> commandLineArguments() {
std::vector<std::string> res;
#ifdef _WIN32
int argc;
auto argvW = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argvW == nullptr)
return {};
res.reserve(argc);
for (int i = 0; i != argc; ++i) {
res.push_back(wideToUtf8(argvW[i]));
}
::LocalFree(argvW);
#elif defined(__APPLE__)
auto argv = *(_NSGetArgv());
for (int i = 0; argv[i] != nullptr; ++i) {
res.push_back(argv[i]);
}
#else
std::ifstream file("/proc/self/cmdline", std::ios::in);
if (file.fail())
return {};
std::string s;
while (std::getline(file, s, '\0')) {
res.push_back(s);
}
file.close();
#endif
return res;
}
class PrintScopeGuard {
public:
static std::mutex &global_mtx() {
static std::mutex _instance;
return _instance;
}
explicit PrintScopeGuard(int foreground, int background)
: consoleChanged(!(foreground == DefaultColor && background == DefaultColor)) {
global_mtx().lock();
#ifdef _WIN32
_codepage = ::GetConsoleOutputCP();
::SetConsoleOutputCP(CP_UTF8);
if (consoleChanged) {
WORD winColor = 0;
if (foreground != DefaultColor) {
winColor |= (foreground & Intensified) ? FOREGROUND_INTENSITY : 0;
switch (foreground & 0xF) {
case Red:
winColor |= FOREGROUND_RED;
break;
case Green:
winColor |= FOREGROUND_GREEN;
break;
case Blue:
winColor |= FOREGROUND_BLUE;
break;
case Yellow:
winColor |= FOREGROUND_RED | FOREGROUND_GREEN;
break;
case Purple:
winColor |= FOREGROUND_RED | FOREGROUND_BLUE;
break;
case Cyan:
winColor |= FOREGROUND_GREEN | FOREGROUND_BLUE;
break;
case White:
winColor |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
default:
break;
}
}
if (background != DefaultColor) {
winColor |= (background & Intensified) ? BACKGROUND_INTENSITY : 0;
switch (background & 0xF) {
case Red:
winColor |= BACKGROUND_RED;
break;
case Green:
winColor |= BACKGROUND_GREEN;
break;
case Blue:
winColor |= BACKGROUND_BLUE;
break;
case Yellow:
winColor |= BACKGROUND_RED | BACKGROUND_GREEN;
break;
case Purple:
winColor |= BACKGROUND_RED | BACKGROUND_BLUE;
break;
case Cyan:
winColor |= BACKGROUND_GREEN | BACKGROUND_BLUE;
break;
case White:
winColor |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
default:
break;
}
}
_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(_hConsole, &_csbi);
SetConsoleTextAttribute(_hConsole, winColor);
}
#else
if (consoleChanged) {
const char *strList[3];
int strListSize = 0;
if (foreground != DefaultColor) {
bool light = foreground & Intensified;
const char *colorStr = nullptr;
switch (foreground & 0xF) {
case Red:
colorStr = light ? "91" : "31";
break;
case Green:
colorStr = light ? "92" : "32";
break;
case Blue:
colorStr = light ? "94" : "34";
break;
case Yellow:
colorStr = light ? "93" : "33";
break;
case Purple:
colorStr = light ? "95" : "35";
break;
case Cyan:
colorStr = light ? "96" : "36";
break;
case White:
colorStr = light ? "97" : "37";
break;
default:
break;
}
if (colorStr) {
strList[strListSize] = colorStr;
strListSize++;
}
}
if (background != DefaultColor) {
bool light = background & Intensified;
const char *colorStr = nullptr;
switch (background & 0xF) {
case Red:
colorStr = light ? "101" : "41";
break;
case Green:
colorStr = light ? "102" : "42";
break;
case Blue:
colorStr = light ? "104" : "44";
break;
case Yellow:
colorStr = light ? "103" : "43";
break;
case Purple:
colorStr = light ? "105" : "45";
break;
case Cyan:
colorStr = light ? "106" : "46";
break;
case White:
colorStr = light ? "107" : "47";
break;
default:
break;
}
if (colorStr) {
strList[strListSize] = colorStr;
strListSize++;
}
}
if (strListSize > 0) {
char buf[20];
int bufSize = 0;
auto buf_puts = [&buf, &bufSize](const char *s) {
auto len = strlen(s);
for (; *s != '\0'; ++s) {
buf[bufSize++] = *s;
}
};
buf_puts("\033[");
for (int i = 0; i < strListSize - 1; ++i) {
buf_puts(strList[i]);
buf_puts(";");
}
buf_puts(strList[strListSize - 1]);
buf_puts("m");
buf[bufSize] = '\0';
printf("%s", buf);
}
}
#endif
}
~PrintScopeGuard() {
#ifdef _WIN32
::SetConsoleOutputCP(_codepage);
if (consoleChanged) {
SetConsoleTextAttribute(_hConsole, _csbi.wAttributes);
}
#else
if (consoleChanged) {
// ANSI escape code to reset text color to default
const char *resetColor = "\033[0m";
printf("%s", resetColor);
}
#endif
global_mtx().unlock();
}
private:
bool consoleChanged;
#ifdef _WIN32
UINT _codepage;
HANDLE _hConsole;
CONSOLE_SCREEN_BUFFER_INFO _csbi;
#endif
};
int u8printf(int foreground, int background, const char *fmt, ...) {
PrintScopeGuard _guard(foreground, background);
va_list args;
va_start(args, fmt);
int res = std::vprintf(fmt, args);
va_end(args);
return res;
}
int u8vprintf(int foreground, int background, const char *fmt, va_list args) {
PrintScopeGuard _guard(foreground, background);
return std::vprintf(fmt, args);
}
/*!
Prints the formatted text in UTF-8 with given message type.
*/
int u8debug(MessageType messageType, bool highlight, const char *fmt, ...) {
int color;
switch (messageType) {
case MT_Message:
color = White;
break;
case MT_Healthy:
color = Green;
break;
case MT_Warning:
color = Yellow;
break;
case MT_Critical:
color = Red;
break;
default:
color = DefaultColor;
break;
}
if (highlight) {
color |= Intensified;
}
va_list args;
va_start(args, fmt);
int res = u8vprintf(color, DefaultColor, fmt, args);
va_end(args);
return res;
}
int u8info(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int res = u8vprintf(DefaultColor, DefaultColor, fmt, args);
va_end(args);
return res;
}
}
@@ -0,0 +1,104 @@
#include "utils_p.h"
#include <algorithm>
namespace SysCmdLine::Utils {
std::vector<std::string> split(const std::string &s, const std::string &delimiter) {
std::vector<std::string> tokens;
std::string::size_type start = 0;
std::string::size_type end = s.find(delimiter);
while (end != std::string::npos) {
tokens.push_back(s.substr(start, end - start));
start = end + delimiter.size();
end = s.find(delimiter, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
std::string join(const std::vector<std::string> &v, const std::string &delimiter) {
if (v.empty())
return {};
std::string res;
for (int i = 0; i < v.size() - 1; ++i) {
res.append(v[i]);
res.append(delimiter);
}
res.append(v.back());
return res;
}
std::string formatText(const std::string &format, const std::vector<std::string> &args) {
std::string result = format;
for (size_t i = 0; i < args.size(); i++) {
std::string placeholder = "%" + std::to_string(i + 1);
size_t pos = result.find(placeholder);
while (pos != std::string::npos) {
result.replace(pos, placeholder.length(), args[i]);
pos = result.find(placeholder, pos + args[i].size());
}
}
return result;
}
static int levenshteinDistance(const std::string &s1, const std::string &s2) {
auto len1 = s1.size();
auto len2 = s2.size();
// Alloc
int **dp = new int *[len1 + 1];
for (int i = 0; i <= len1; i++) {
dp[i] = new int[len2 + 1];
}
for (int i = 0; i <= len1; i++) {
for (int j = 0; j <= len2; j++) {
if (i == 0) {
dp[i][j] = j;
} else if (j == 0) {
dp[i][j] = i;
} else if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
}
}
}
int res = dp[len1][len2];
// Free
for (int i = 0; i <= len1; i++) {
delete[] dp[i];
}
delete[] dp;
return res;
}
std::vector<std::string> calcClosestTexts(const std::vector<std::string> &texts,
const std::string &input, int threshold) {
std::vector<std::string> suggestions;
for (const auto &cmd : texts) {
int distance = levenshteinDistance(input, cmd);
if (distance <= threshold) {
suggestions.push_back(cmd);
}
}
return suggestions;
}
std::string toUpper(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
}
}
@@ -0,0 +1,69 @@
#ifndef UTILS_H
#define UTILS_H
#include <cstring>
#include <string>
#include <vector>
namespace SysCmdLine::Utils {
std::vector<std::string> split(const std::string &s, const std::string &delimiter);
std::string join(const std::vector<std::string> &v, const std::string &delimiter);
std::string formatText(const std::string &format, const std::vector<std::string> &args);
std::vector<std::string> calcClosestTexts(const std::vector<std::string> &texts,
const std::string &input, int threshold);
std::string toUpper(std::string s);
std::string toLower(std::string s);
inline bool starts_with(const std::string_view &s, const std::string_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
template <class Container, class T>
inline bool contains(const Container &container, const T &key) {
#if __cplusplus >= 202002L
return container.contains(key);
#else
return container.count(key) != 0;
#endif
}
template <class T>
std::vector<T> concatVector(const std::vector<T> &v1, const std::vector<T> &v2) {
std::vector<T> res;
res.reserve(v1.size() + v2.size());
for (const auto &item : v1)
res.emplace_back(item);
for (const auto &item : v2)
res.emplace_back(item);
return res;
}
template <class T>
std::basic_string<T> trim(const std::basic_string<T> &str) {
auto start = str.begin();
while (start != str.end() && std::isspace(*start)) {
start++;
}
auto end = str.end();
do {
end--;
} while (std::distance(start, end) > 0 && std::isspace(*end));
return {start, end + 1};
}
}
#endif // UTILS_H
@@ -0,0 +1,360 @@
#include "value.h"
#include <algorithm>
#include "utils_p.h"
namespace SysCmdLine {
static constexpr const char True_Literal[] = "true";
static constexpr const char False_Literal[] = "false";
Value::Value(Value::Type type) : _type(type) {
switch (type) {
case Bool:
data.b = false;
break;
case Int:
data.i = 0;
break;
case Int64:
data.l = 0;
break;
case Double:
data.d = 0;
break;
case String:
data.s = new std::string();
break;
default:
break;
}
}
Value::~Value() {
if (_type == String)
delete data.s;
}
Value::Value(const Value &other) {
_type = other._type;
switch (_type) {
case Bool:
data.b = other.data.b;
break;
case Int:
data.i = other.data.i;
break;
case Int64:
data.l = other.data.l;
break;
case Double:
data.d = other.data.d;
break;
case String:
data.s = new std::string(*other.data.s);
break;
default:
break;
}
}
Value::Value(Value &&other) noexcept {
_type = other._type;
std::swap(data, other.data);
other._type = Null;
}
Value &Value::operator=(const Value &other) {
if (this == &other) {
return *this;
}
if (_type == String) {
delete data.s;
}
_type = other._type;
switch (_type) {
case Bool:
data.b = other.data.b;
break;
case Int:
data.i = other.data.i;
break;
case Int64:
data.l = other.data.l;
break;
case Double:
data.d = other.data.d;
break;
case String:
data.s = new std::string(*other.data.s);
break;
default:
break;
}
return *this;
}
Value &Value::operator=(Value &&other) noexcept {
if (this == &other) {
return *this;
}
std::swap(data, other.data);
std::swap(_type, other._type);
return *this;
}
bool Value::isEmpty() const {
switch (_type) {
case Bool:
return !data.b;
case Int:
return data.i == 0;
case Int64:
return data.l == 0;
case Double:
return data.d == 0;
case String:
return data.s->empty();
default:
break;
}
return true;
}
bool Value::toBool() const {
return _type == Bool && data.b;
}
int Value::toInt() const {
switch (_type) {
case Bool:
return data.b;
case Int:
return data.i;
case Int64:
return int(data.l);
case Double:
return int(data.d);
default:
break;
}
return 0;
}
int64_t Value::toInt64() const {
switch (_type) {
case Bool:
return data.b;
case Int:
return data.i;
case Int64:
return data.l;
case Double:
return int64_t(data.d);
default:
break;
}
return 0;
}
double Value::toDouble() const {
switch (_type) {
case Bool:
return data.b;
case Int:
return data.i;
case Int64:
return double(data.l);
case Double:
return data.d;
default:
break;
}
return 0;
}
std::string Value::toString() const {
switch (_type) {
case Bool:
return data.b ? True_Literal : False_Literal;
case Int:
return std::to_string(data.i);
case Int64:
return std::to_string(data.l);
case Double:
return std::to_string(data.d);
case String:
return *data.s;
default:
break;
}
return {};
}
bool Value::operator==(const Value &other) const {
if (_type != other._type)
return false;
switch (_type) {
case Bool:
return data.b == other.data.b;
case Int:
return data.i == other.data.i;
case Int64:
return data.l == other.data.l;
case Double:
return data.d == other.data.d;
case String:
return *data.s == *other.data.s;
default:
break;
}
return true;
}
bool Value::operator!=(const Value &other) const {
return !(*this == other);
}
static void determineBase(std::string &s, int &base, int &f) {
if (s.front() == '+') {
s = s.substr(1);
} else if (s.front() == '-') {
f = -1;
s = s.substr(1);
}
if (s.size() > 2 && s.front() == '0') {
switch (s.at(1)) {
case 'x':
case 'X':
s = s.substr(2);
base = 16;
break;
case 'b':
case 'B':
s = s.substr(2);
base = 2;
break;
case 'o':
case 'O':
s = s.substr(2);
base = 8;
break;
case 'd':
case 'D':
s = s.substr(2);
break;
default:
break;
}
}
}
Value Value::fromString(const std::string &s, Value::Type type) {
Value res;
switch (type) {
case Bool: {
if (Utils::toLower(s) == True_Literal) {
res = true;
} else if (Utils::toLower(s) == False_Literal) {
res = false;
}
break;
}
case Int: {
std::string::size_type idx;
try {
std::string s1 = s;
int base = 10;
int f = 1;
determineBase(s1, base, f);
res = std::stoi(s1, &idx, base) * f;
if (idx < s1.size()) {
res = {};
}
} catch (...) {
}
break;
}
case Int64: {
std::string::size_type idx;
try {
std::string s1 = s;
int base = 10;
int f = 1;
determineBase(s1, base, f);
res = (int64_t) std::stoll(s1, &idx, base) * f;
if (idx < s1.size()) {
res = {};
}
} catch (...) {
}
break;
}
case Double: {
std::string::size_type idx;
try {
res = std::stod(s, &idx);
if (idx < s.size()) {
res = {};
}
} catch (...) {
}
break;
}
case String: {
if (!s.empty()) {
res = s;
}
break;
}
default:
break;
}
return res;
}
std::vector<std::string> Value::toStringList(const std::vector<Value> &values) {
std::vector<std::string> res(values.size());
std::transform(values.begin(), values.end(), res.begin(), [](const Value &val) {
return val.toString(); //
});
return res;
}
const char *Value::typeName(Value::Type type) {
const char *expected;
switch (type) {
case Value::Bool: {
expected = "boolean";
break;
}
case Value::Int: {
expected = "int";
break;
}
case Value::Int64: {
expected = "long";
break;
}
case Value::Double: {
expected = "double";
break;
}
case Value::String: {
expected = "string";
break;
}
default:
expected = "null";
break;
}
return expected;
}
}
@@ -0,0 +1,5 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/syscmdlineTargets.cmake")
@@ -0,0 +1 @@
add_subdirectory(basic)
@@ -0,0 +1,7 @@
project(tst_basic)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)
@@ -0,0 +1,102 @@
#include <iostream>
#include <cassert>
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
using namespace SysCmdLine;
int main(int argc, char *argv[]) {
SYSCMDLINE_UNUSED(argc);
SYSCMDLINE_UNUSED(argv);
{
std::cout << "[Test Simple]" << std::endl;
Command cmd("cmd");
cmd.addArguments({
{"arg1"},
{"arg2"},
});
{
Parser parser(cmd);
ParseResult res = parser.parse({"cmd", "1"});
assert(res.error() == ParseResult::MissingCommandArgument);
}
std::cout << "Missing argument: OK" << std::endl;
{
Parser parser(cmd);
ParseResult res = parser.parse({"cmd", "1", "2", "3"});
assert(res.error() == ParseResult::TooManyArguments);
}
std::cout << "Too many argument: OK" << std::endl;
{
Parser parser(cmd);
ParseResult res = parser.parse({"cmd", "1", "2"});
assert(res.error() == ParseResult::NoError);
assert(res.value("arg1") == "1");
assert(res.value("arg2") == "2");
}
std::cout << "Get positional arguments: OK" << std::endl;
}
std::cout << std::endl;
{
std::cout << "[Test Special Argument]" << std::endl;
Argument arg1("arg1");
arg1.setExpectedValues({"1", "2", "3"});
Argument arg2("arg2");
arg2.setRequired(false);
arg2.setDefaultValue(10);
Command cmd("cmd");
cmd.addArguments({
arg1,
arg2,
});
{
Parser parser(cmd);
ParseResult res = parser.parse({"cmd", "4"});
assert(res.error() == ParseResult::InvalidArgumentValue);
}
std::cout << "Unexpected argument: OK" << std::endl;
{
Parser parser(cmd);
ParseResult res = parser.parse({"cmd", "1", "str"});
assert(res.error() == ParseResult::ArgumentTypeMismatch);
}
std::cout << "Argument type mismatch: OK" << std::endl;
}
std::cout << std::endl;
{
std::cout << "[Test Exclusive Option]" << std::endl;
Option opt1("--opt1", "1");
Option opt2("--opt2", "2");
Command cmd("cmd");
cmd.addOptions({opt1, opt2}, "1");
{
Parser parser(cmd);
ParseResult res = parser.parse({"cmd", "--opt1", "--opt2"});
assert(res.error() == ParseResult::MutuallyExclusiveOptions);
}
std::cout << "Mutually exclusive options: OK" << std::endl;
}
std::cout << std::endl;
{
std::cout << "[Test Short Option]" << std::endl;
}
return 0;
}