chore: baseline usable version
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ×);
|
||||
|
||||
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 ×) {
|
||||
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 ×) {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user