1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00

feat: unicode support (#804)

* Add unicode support tests

* Add unicode parse tests

* Implement #14

* Slim down Windows.h

* Fix documentation comments

* Fix clang-tidy and cpplint

* Update README

* Fix clang-tidy

* Fix to_path not being available on linux

* Add roundtrip encoding tests

* style: pre-commit.ci fixes

* Fix pre-commit.ci

* Fix codacy

* Exclude parse_unicode which should not contain a newline from pre-commit

* Remove a test which breaks CI

* Fix build in CI

* Replace broken execute_with tests

* Fix wide string conversions on all systems

* Fix system args on apple

* style: pre-commit.ci fixes

* Fix some includes

* Fix wrong size calculation and comments

* Add guards around codecvt

* Fix _Pragma not recognized on MSVC

* Fix bad macro check

* Fix include

* Fix narrow and widen when codecvt is missing

* Fix some weird bug in old MSVC

* Add dependent applications to meson-build

* Fix precompilation

* Fix lint

* Fix coverage

* Update README

* style: pre-commit.ci fixes

* Fix lint

* Fix coverage

* Fix optional braces offending clang

* Remove copied comments from Windows.h

* Suppress flawfinder detects

* Fix cmake config tests failing because of a missing lib

* chore: update copyright on new files to 2023

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* style: pre-commit.ci fixes

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
This commit is contained in:
Andrey Zhukov 2023-01-12 23:03:20 +03:00 committed by GitHub
parent 48624ead3c
commit a227cd10fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1093 additions and 57 deletions

View File

@ -128,9 +128,9 @@ jobs:
- name: Build - name: Build
run: meson compile -C build-meson run: meson compile -C build-meson
cmake-config: cmake-config-ubuntu-1804:
name: CMake config check name: CMake config check (Ubuntu 18.04)
runs-on: ubuntu-20.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -175,6 +175,12 @@ jobs:
cmake-version: "3.10" cmake-version: "3.10"
if: success() || failure() if: success() || failure()
cmake-config-ubuntu-2004:
name: CMake config check (Ubuntu 20.04)
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Check CMake 3.11 (full) - name: Check CMake 3.11 (full)
uses: ./.github/actions/quick_cmake uses: ./.github/actions/quick_cmake
with: with:
@ -212,6 +218,12 @@ jobs:
cmake-version: "3.16" cmake-version: "3.16"
if: success() || failure() if: success() || failure()
cmake-config-ubuntu-2204:
name: CMake config check (Ubuntu 22.04)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Check CMake 3.17 - name: Check CMake 3.17
uses: ./.github/actions/quick_cmake uses: ./.github/actions/quick_cmake
with: with:

2
.gitignore vendored
View File

@ -6,8 +6,10 @@ a.out*
/CMakeFiles/* /CMakeFiles/*
/cmake_install.cmake /cmake_install.cmake
/*.kdev4 /*.kdev4
/.vscode
/html/* /html/*
!/meson.build !/meson.build
/CMakeUserPresets.json
/node_modules/* /node_modules/*
/package.json /package.json

View File

@ -40,10 +40,24 @@
{macros_hpp} {macros_hpp}
{slim_windows_h_hpp}
{validators_hpp_filesystem} {validators_hpp_filesystem}
{encoding_includes}
{argv_inl_includes}
namespace {namespace} {{ namespace {namespace} {{
{encoding_hpp}
{encoding_inl_hpp}
{argv_hpp}
{argv_inl_hpp}
{string_tools_hpp} {string_tools_hpp}
{string_tools_inl_hpp} {string_tools_inl_hpp}

View File

@ -4,6 +4,7 @@ linelength=120 # As in .clang-format
# Unused filters # Unused filters
filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects. filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects.
filter=-build/include_order # Requires unusual include order that encourages creating not self-contained headers filter=-build/include_order # Requires unusual include order that encourages creating not self-contained headers
filter=-build/include_subdir # Prevents including files in current directory for whatever reason
filter=-readability/nolint # Conflicts with clang-tidy filter=-readability/nolint # Conflicts with clang-tidy
filter=-readability/check # Catch uses CHECK(a == b) (Tests only) filter=-readability/check # Catch uses CHECK(a == b) (Tests only)
filter=-build/namespaces # Currently using it for one test (Tests only) filter=-build/namespaces # Currently using it for one test (Tests only)

119
README.md
View File

@ -50,6 +50,7 @@ set with a simple and intuitive interface.
- [Formatting](#formatting) - [Formatting](#formatting)
- [Subclassing](#subclassing) - [Subclassing](#subclassing)
- [How it works](#how-it-works) - [How it works](#how-it-works)
- [Unicode support](#unicode-support)
- [Utilities](#utilities) - [Utilities](#utilities)
- [Other libraries](#other-libraries) - [Other libraries](#other-libraries)
- [API](#api) - [API](#api)
@ -164,9 +165,6 @@ this library:
option to disable it). option to disable it).
- Autocomplete: This might eventually be added to both Plumbum and CLI11, but it - Autocomplete: This might eventually be added to both Plumbum and CLI11, but it
is not supported yet. is not supported yet.
- Wide strings / unicode: Since this uses the standard library only, it might be
hard to properly implement, but I would be open to suggestions in how to do
this.
## Install ## Install
@ -278,13 +276,13 @@ To set up, add options, and run, your main function will look something like
this: this:
```cpp ```cpp
int main(int argc, char** argv) { int main() {
CLI::App app{"App description"}; CLI::App app{"App description"};
std::string filename = "default"; std::string filename = "default";
app.add_option("-f,--file", filename, "A help string"); app.add_option("-f,--file", filename, "A help string");
CLI11_PARSE(app, argc, argv); CLI11_PARSE(app);
return 0; return 0;
} }
``` ```
@ -293,7 +291,7 @@ int main(int argc, char** argv) {
```cpp ```cpp
try { try {
app.parse(argc, argv); app.parse();
} catch (const CLI::ParseError &e) { } catch (const CLI::ParseError &e) {
return app.exit(e); return app.exit(e);
} }
@ -306,6 +304,25 @@ inside the catch block; for example, help flags intentionally short-circuit all
other processing for speed and to ensure required options and the like do not other processing for speed and to ensure required options and the like do not
interfere. interfere.
</p></details>
<details><summary>Note: Why are argc and argv not used? (click to expand)</summary><p>
`argc` and `argv` may contain incorrect information on Windows when unicode text
is passed in. Check out a section on [unicode support](#unicode-support) below.
If this is not a concern, you can explicitly pass `argc` and `argv` from main or
from an external preprocessor of CLI arguments to `parse`:
```cpp
int main(int argc, char** argv) {
// ...
CLI11_PARSE(app, argc, argv);
return 0;
}
```
</p></details> </p></details>
</br> </br>
@ -1468,6 +1485,96 @@ app.add_option("--fancy-count", [](std::vector<std::string> val){
}); });
``` ```
### Unicode support
CLI11 supports Unicode and wide strings as defined in the
[UTF-8 Everywhere](http://utf8everywhere.org/) manifesto. In particular:
- The library can parse a wide version of command-line arguments on Windows,
which are converted internally to UTF-8 (more on this below);
- You can store option values in `std::wstring`, in which case they will be
converted to a correct wide string encoding on your system (UTF-16 on Windows
and UTF-32 on most other systems);
- Instead of storing wide strings, it is recommended to use provided `widen` and
`narrow` functions to convert to and from wide strings when actually necessary
(such as when calling into Windows APIs).
When using the command line on Windows with unicode arguments, your `main`
function may already receive broken Unicode. Parsing `argv` at that point will
not give you a correct string. To fix this, you have three options:
1. If you pass unmodified command-line arguments to CLI11, call `app.parse()`
instead of `app.parse(argc, argv)` (or `CLI11_PARSE(app)` instead of
`CLI11_PARSE(app, argc, argv)`). The library will find correct arguments
itself.
```cpp
int main() {
CLI::App app;
// ...
CLI11_PARSE(app);
}
```
2. Get correct arguments with which the program was originally executed using
provided functions: `CLI::argc()` and `CLI::argv()`. These two methods are
the only cross-platform ways of handling unicode correctly.
```cpp
int main() {
CLI::App app;
// ...
CLI11_PARSE(app, CLI::argc(), CLI::argv());
}
```
3. Use the Windows-only non-standard `wmain` function, which accepts
`wchar_t *argv[]` instead of `char* argv[]`. Parsing this will allow CLI to
convert wide strings to UTF-8 without losing information.
```cpp
int wmain(int argc, wchar_t *argv[]) {
CLI::App app;
// ...
CLI11_PARSE(app, argc, argv);
}
```
4. Retrieve arguments yourself by using Windows APIs like
[`CommandLineToArgvW`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw)
and pass them to CLI. This is what the library is doing under the hood in
`CLI::argv()`.
The library provides functions to convert between UTF-8 and wide strings:
```cpp
namespace CLI {
std::string narrow(const std::wstring &str);
std::string narrow(const wchar_t *str);
std::string narrow(const wchar_t *str, std::size_t size);
std::string narrow(std::wstring_view str); // C++17
std::wstring widen(const std::string &str);
std::wstring widen(const char *str);
std::wstring widen(const char *str, std::size_t size);
std::wstring widen(std::string_view str); // C++17
}
```
#### Note on using Unicode paths
When creating a `filesystem::path` from a UTF-8 path on Windows, you need to
convert it to a wide string first. CLI11 provides a platform-independent
`to_path` function, which will convert a UTF-8 string to path, the right way:
```cpp
std::string utf8_name = "Hello Halló Привет 你好 👩‍🚀❤️.txt";
std::filesystem::path p = CLI::to_path(utf8_name);
std::ifstream stream(CLI::to_path(utf8_name));
// etc.
```
### Utilities ### Utilities
There are a few other utilities that are often useful in CLI programming. These There are a few other utilities that are often useful in CLI programming. These

View File

@ -35,9 +35,9 @@ namespace CLI {
// [CLI11:app_hpp:verbatim] // [CLI11:app_hpp:verbatim]
#ifndef CLI11_PARSE #ifndef CLI11_PARSE
#define CLI11_PARSE(app, argc, argv) \ #define CLI11_PARSE(app, ...) \
try { \ try { \
(app).parse((argc), (argv)); \ (app).parse(__VA_ARGS__); \
} catch(const CLI::ParseError &e) { \ } catch(const CLI::ParseError &e) { \
return (app).exit(e); \ return (app).exit(e); \
} }
@ -837,15 +837,25 @@ class App {
/// Reset the parsed data /// Reset the parsed data
void clear(); void clear();
/// Parse the command-line arguments passed to the main function of the executable.
/// This overload will correctly parse unicode arguments on Windows.
void parse();
/// Parses the command line - throws errors. /// Parses the command line - throws errors.
/// This must be called after the options are in but before the rest of the program. /// This must be called after the options are in but before the rest of the program.
void parse(int argc, const char *const *argv); void parse(int argc, const char *const *argv);
void parse(int argc, const wchar_t *const *argv);
private:
template <class CharT> void parse_char_t(int argc, const CharT *const *argv);
public:
/// Parse a single string as if it contained command line arguments. /// Parse a single string as if it contained command line arguments.
/// This function splits the string into arguments then calls parse(std::vector<std::string> &) /// This function splits the string into arguments then calls parse(std::vector<std::string> &)
/// the function takes an optional boolean argument specifying if the programName is included in the string to /// the function takes an optional boolean argument specifying if the programName is included in the string to
/// process /// process
void parse(std::string commandline, bool program_name_included = false); void parse(std::string commandline, bool program_name_included = false);
void parse(std::wstring commandline, bool program_name_included = false);
/// The real work is done here. Expects a reversed vector. /// The real work is done here. Expects a reversed vector.
/// Changes the vector to the remaining options. /// Changes the vector to the remaining options.

25
include/CLI/Argv.hpp Normal file
View File

@ -0,0 +1,25 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <CLI/Macros.hpp>
namespace CLI {
// [CLI11:argv_hpp:verbatim]
/// argc as passed in to this executable.
CLI11_INLINE int argc();
/// argv as passed in to this executable, converted to utf-8 on Windows.
CLI11_INLINE const char *const *argv();
// [CLI11:argv_hpp:end]
} // namespace CLI
#ifndef CLI11_COMPILE
#include "impl/Argv_inl.hpp"
#endif

View File

@ -13,6 +13,10 @@
#include "Macros.hpp" #include "Macros.hpp"
#include "Encoding.hpp"
#include "Argv.hpp"
#include "StringTools.hpp" #include "StringTools.hpp"
#include "Error.hpp" #include "Error.hpp"

54
include/CLI/Encoding.hpp Normal file
View File

@ -0,0 +1,54 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <CLI/Macros.hpp>
// [CLI11:public_includes:set]
#include <string>
// [CLI11:public_includes:end]
// [CLI11:encoding_includes:verbatim]
#ifdef CLI11_CPP17
#include <string_view>
#endif // CLI11_CPP17
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem>
#include <string_view> // NOLINT(build/include)
#endif // CLI11_HAS_FILESYSTEM
// [CLI11:encoding_includes:end]
namespace CLI {
// [CLI11:encoding_hpp:verbatim]
/// Convert a wide string to a narrow string.
CLI11_INLINE std::string narrow(const std::wstring &str);
CLI11_INLINE std::string narrow(const wchar_t *str);
CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size);
/// Convert a narrow string to a wide string.
CLI11_INLINE std::wstring widen(const std::string &str);
CLI11_INLINE std::wstring widen(const char *str);
CLI11_INLINE std::wstring widen(const char *str, std::size_t size);
#ifdef CLI11_CPP17
CLI11_INLINE std::string narrow(std::wstring_view str);
CLI11_INLINE std::wstring widen(std::string_view str);
#endif // CLI11_CPP17
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
/// Convert a char-string to a native path correctly.
CLI11_INLINE std::filesystem::path to_path(std::string_view str);
#endif // CLI11_HAS_FILESYSTEM
// [CLI11:encoding_hpp:end]
} // namespace CLI
#ifndef CLI11_COMPILE
#include "impl/Encoding_inl.hpp"
#endif

View File

@ -66,6 +66,62 @@
#endif #endif
#endif #endif
/** <filesystem> availability */
#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
#if __has_include(<filesystem>)
// Filesystem cannot be used if targeting macOS < 10.15
#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
#define CLI11_HAS_FILESYSTEM 0
#elif defined(__wasi__)
// As of wasi-sdk-14, filesystem is not implemented
#define CLI11_HAS_FILESYSTEM 0
#else
#include <filesystem>
#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
#define CLI11_HAS_FILESYSTEM 1
#elif defined(__GLIBCXX__)
// if we are using gcc and Version <9 default to no filesystem
#define CLI11_HAS_FILESYSTEM 0
#else
#define CLI11_HAS_FILESYSTEM 1
#endif
#else
#define CLI11_HAS_FILESYSTEM 0
#endif
#endif
#endif
#endif
/** <codecvt> availability */
#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5
#define CLI11_HAS_CODECVT 0
#else
#define CLI11_HAS_CODECVT 1
#include <codecvt>
#endif
/** disable deprecations */
#if defined(__GNUC__) // GCC or clang
#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#elif defined(_MSC_VER)
#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push))
#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop))
#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996))
#else
#define CLI11_DIAGNOSTIC_PUSH
#define CLI11_DIAGNOSTIC_POP
#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED
#endif
/** Inline macro **/ /** Inline macro **/
#ifdef CLI11_COMPILE #ifdef CLI11_COMPILE
#define CLI11_INLINE #define CLI11_INLINE

View File

@ -18,6 +18,7 @@
#include <vector> #include <vector>
// [CLI11:public_includes:end] // [CLI11:public_includes:end]
#include "Encoding.hpp"
#include "StringTools.hpp" #include "StringTools.hpp"
namespace CLI { namespace CLI {
@ -242,8 +243,10 @@ struct is_mutable_container<
decltype(std::declval<T>().clear()), decltype(std::declval<T>().clear()),
decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
std::declval<const typename T::value_type &>()))>, std::declval<const typename T::value_type &>()))>,
void>> void>> : public conditional_t<std::is_constructible<T, std::string>::value ||
: public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {}; std::is_constructible<T, std::wstring>::value,
std::false_type,
std::true_type> {};
// check to see if an object is a mutable container (fail by default) // check to see if an object is a mutable container (fail by default)
template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
@ -546,6 +549,8 @@ enum class object_category : int {
// string like types // string like types
string_assignable = 23, string_assignable = 23,
string_constructible = 24, string_constructible = 24,
wstring_assignable = 25,
wstring_constructible = 26,
other = 45, other = 45,
// special wrapper or container types // special wrapper or container types
wrapper_value = 50, wrapper_value = 50,
@ -613,6 +618,27 @@ struct classify_object<
static constexpr object_category value{object_category::string_constructible}; static constexpr object_category value{object_category::string_constructible};
}; };
/// Wide strings
template <typename T>
struct classify_object<T,
typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value &&
!std::is_constructible<T, std::string>::value &&
std::is_assignable<T &, std::wstring>::value>::type> {
static constexpr object_category value{object_category::wstring_assignable};
};
template <typename T>
struct classify_object<
T,
typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value &&
!std::is_constructible<T, std::string>::value &&
!std::is_assignable<T &, std::wstring>::value && (type_count<T>::value == 1) &&
std::is_constructible<T, std::wstring>::value>::type> {
static constexpr object_category value{object_category::wstring_constructible};
};
/// Enumerations /// Enumerations
template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> { template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> {
static constexpr object_category value{object_category::enumeration}; static constexpr object_category value{object_category::enumeration};
@ -625,12 +651,13 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_compl
/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
/// vectors, and enumerations /// vectors, and enumerations
template <typename T> struct uncommon_type { template <typename T> struct uncommon_type {
using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && using type = typename std::conditional<
!std::is_assignable<T &, std::string>::value && !std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_constructible<T, std::string>::value && !is_complex<T>::value && !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value &&
!is_mutable_container<T>::value && !std::is_enum<T>::value, !std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value &&
std::true_type, !is_complex<T>::value && !is_mutable_container<T>::value && !std::is_enum<T>::value,
std::false_type>::type; std::true_type,
std::false_type>::type;
static constexpr bool value = type::value; static constexpr bool value = type::value;
}; };
@ -1005,6 +1032,23 @@ bool lexical_cast(const std::string &input, T &output) {
return true; return true;
} }
/// Wide strings
template <
typename T,
enable_if_t<classify_object<T>::value == object_category::wstring_assignable, detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
output = widen(input);
return true;
}
template <
typename T,
enable_if_t<classify_object<T>::value == object_category::wstring_constructible, detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
output = T{widen(input)};
return true;
}
/// Enumerations /// Enumerations
template <typename T, template <typename T,
enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
@ -1133,7 +1177,9 @@ template <typename AssignTo,
typename ConvertTo, typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value && enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
(classify_object<AssignTo>::value == object_category::string_assignable || (classify_object<AssignTo>::value == object_category::string_assignable ||
classify_object<AssignTo>::value == object_category::string_constructible), classify_object<AssignTo>::value == object_category::string_constructible ||
classify_object<AssignTo>::value == object_category::wstring_assignable ||
classify_object<AssignTo>::value == object_category::wstring_constructible),
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) { bool lexical_assign(const std::string &input, AssignTo &output) {
return lexical_cast(input, output); return lexical_cast(input, output);
@ -1144,7 +1190,9 @@ template <typename AssignTo,
typename ConvertTo, typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value && enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value &&
classify_object<AssignTo>::value != object_category::string_assignable && classify_object<AssignTo>::value != object_category::string_assignable &&
classify_object<AssignTo>::value != object_category::string_constructible, classify_object<AssignTo>::value != object_category::string_constructible &&
classify_object<AssignTo>::value != object_category::wstring_assignable &&
classify_object<AssignTo>::value != object_category::wstring_constructible,
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) { bool lexical_assign(const std::string &input, AssignTo &output) {
if(input.empty()) { if(input.empty()) {

View File

@ -26,34 +26,6 @@
// [CLI11:validators_hpp_filesystem:verbatim] // [CLI11:validators_hpp_filesystem:verbatim]
// C standard library
// Only needed for existence checking
#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
#if __has_include(<filesystem>)
// Filesystem cannot be used if targeting macOS < 10.15
#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
#define CLI11_HAS_FILESYSTEM 0
#elif defined(__wasi__)
// As of wasi-sdk-14, filesystem is not implemented
#define CLI11_HAS_FILESYSTEM 0
#else
#include <filesystem>
#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
#define CLI11_HAS_FILESYSTEM 1
#elif defined(__GLIBCXX__)
// if we are using gcc and Version <9 default to no filesystem
#define CLI11_HAS_FILESYSTEM 0
#else
#define CLI11_HAS_FILESYSTEM 1
#endif
#else
#define CLI11_HAS_FILESYSTEM 0
#endif
#endif
#endif
#endif
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem> // NOLINT(build/include) #include <filesystem> // NOLINT(build/include)
#else #else

View File

@ -9,6 +9,9 @@
// This include is only needed for IDEs to discover symbols // This include is only needed for IDEs to discover symbols
#include <CLI/App.hpp> #include <CLI/App.hpp>
#include <CLI/Argv.hpp>
#include <CLI/Encoding.hpp>
// [CLI11:public_includes:set] // [CLI11:public_includes:set]
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
@ -474,17 +477,31 @@ CLI11_INLINE void App::clear() {
} }
} }
CLI11_INLINE void App::parse(int argc, const char *const *argv) { CLI11_INLINE void App::parse() { parse(argc(), argv()); } // LCOV_EXCL_LINE
CLI11_INLINE void App::parse(int argc, const char *const *argv) { parse_char_t(argc, argv); }
CLI11_INLINE void App::parse(int argc, const wchar_t *const *argv) { parse_char_t(argc, argv); }
namespace detail {
// Do nothing or perform narrowing
CLI11_INLINE const char *maybe_narrow(const char *str) { return str; }
CLI11_INLINE std::string maybe_narrow(const wchar_t *str) { return narrow(str); }
} // namespace detail
template <class CharT> CLI11_INLINE void App::parse_char_t(int argc, const CharT *const *argv) {
// If the name is not set, read from command line // If the name is not set, read from command line
if(name_.empty() || has_automatic_name_) { if(name_.empty() || has_automatic_name_) {
has_automatic_name_ = true; has_automatic_name_ = true;
name_ = argv[0]; name_ = detail::maybe_narrow(argv[0]);
} }
std::vector<std::string> args; std::vector<std::string> args;
args.reserve(static_cast<std::size_t>(argc) - 1U); args.reserve(static_cast<std::size_t>(argc) - 1U);
for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i) for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i)
args.emplace_back(argv[i]); args.emplace_back(detail::maybe_narrow(argv[i]));
parse(std::move(args)); parse(std::move(args));
} }
@ -515,6 +532,10 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included
parse(std::move(args)); parse(std::move(args));
} }
CLI11_INLINE void App::parse(std::wstring commandline, bool program_name_included) {
parse(narrow(commandline), program_name_included);
}
CLI11_INLINE void App::parse(std::vector<std::string> &args) { CLI11_INLINE void App::parse(std::vector<std::string> &args) {
// Clear if parsed // Clear if parsed
if(parsed_ > 0) if(parsed_ > 0)

View File

@ -0,0 +1,162 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// This include is only needed for IDEs to discover symbols
#include <CLI/Argv.hpp>
#include <CLI/Encoding.hpp>
// [CLI11:public_includes:set]
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
// [CLI11:public_includes:end]
#ifdef _WIN32
#include "SlimWindowsH.hpp"
#endif // _WIN32
// [CLI11:argv_inl_includes:verbatim]
#if defined(_WIN32)
#include <processenv.h>
#include <shellapi.h>
#elif defined(__APPLE__)
#include <crt_externs.h>
#endif
// [CLI11:argv_inl_includes:end]
namespace CLI {
// [CLI11:argv_inl_hpp:verbatim]
namespace detail {
#ifdef __APPLE__
// Copy argc and argv as early as possible to avoid modification
static const std::vector<const char *> static_args = [] {
static const std::vector<std::string> static_args_as_strings = [] {
std::vector<std::string> args_as_strings;
int argc = *_NSGetArgc();
char **argv = *_NSGetArgv();
args_as_strings.reserve(static_cast<size_t>(argc));
for(size_t i = 0; i < static_cast<size_t>(argc); i++) {
args_as_strings.push_back(argv[i]);
}
return args_as_strings;
}();
std::vector<const char *> static_args_result;
static_args_result.reserve(static_args_as_strings.size());
for(const auto &arg : static_args_as_strings) {
static_args_result.push_back(arg.data());
}
return static_args_result;
}();
#endif
/// Command-line arguments, as passed in to this executable, converted to utf-8 on Windows.
CLI11_INLINE const std::vector<const char *> &args() {
// This function uses initialization via lambdas extensively to take advantage of the thread safety of static
// variable initialization [stmt.dcl.3]
#ifdef _WIN32
static const std::vector<const char *> static_args = [] {
static const std::vector<std::string> static_args_as_strings = [] {
// On Windows, take arguments from GetCommandLineW and convert them to utf-8.
std::vector<std::string> args_as_strings;
int argc = 0;
auto deleter = [](wchar_t **ptr) { LocalFree(ptr); };
// NOLINTBEGIN(*-avoid-c-arrays)
auto wargv =
std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter);
// NOLINTEND(*-avoid-c-arrays)
if(wargv == nullptr) {
throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError()));
}
args_as_strings.reserve(static_cast<size_t>(argc));
for(size_t i = 0; i < static_cast<size_t>(argc); ++i) {
args_as_strings.push_back(narrow(wargv[i]));
}
return args_as_strings;
}();
std::vector<const char *> static_args_result;
static_args_result.reserve(static_args_as_strings.size());
for(const auto &arg : static_args_as_strings) {
static_args_result.push_back(arg.data());
}
return static_args_result;
}();
return static_args;
#elif defined(__APPLE__)
return static_args;
#else
static const std::vector<const char *> static_args = [] {
static const std::vector<char> static_cmdline = [] {
// On posix, retrieve arguments from /proc/self/cmdline, separated by null terminators.
std::vector<char> cmdline;
auto deleter = [](FILE *f) { std::fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> fp_unique(std::fopen("/proc/self/cmdline", "r"), deleter);
FILE *fp = fp_unique.get();
if(!fp) {
throw std::runtime_error("could not open /proc/self/cmdline for reading"); // LCOV_EXCL_LINE
}
size_t size = 0;
while(std::feof(fp) == 0) {
cmdline.resize(size + 128);
size += std::fread(cmdline.data() + size, 1, 128, fp);
if(std::ferror(fp) != 0) {
throw std::runtime_error("error during reading /proc/self/cmdline"); // LCOV_EXCL_LINE
}
}
cmdline.resize(size);
return cmdline;
}();
std::size_t argc = static_cast<std::size_t>(std::count(static_cmdline.begin(), static_cmdline.end(), '\0'));
std::vector<const char *> static_args_result;
static_args_result.reserve(argc);
for(auto it = static_cmdline.begin(); it != static_cmdline.end();
it = std::find(it, static_cmdline.end(), '\0') + 1) {
static_args_result.push_back(static_cmdline.data() + (it - static_cmdline.begin()));
}
return static_args_result;
}();
return static_args;
#endif
}
} // namespace detail
CLI11_INLINE const char *const *argv() { return detail::args().data(); }
CLI11_INLINE int argc() { return static_cast<int>(detail::args().size()); }
// [CLI11:argv_inl_hpp:end]
} // namespace CLI

View File

@ -0,0 +1,154 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// This include is only needed for IDEs to discover symbols
#include <CLI/Encoding.hpp>
#include <CLI/Macros.hpp>
// [CLI11:public_includes:set]
#include <array>
#include <clocale>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <locale>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
// [CLI11:public_includes:end]
namespace CLI {
// [CLI11:encoding_inl_hpp:verbatim]
namespace detail {
#if !CLI11_HAS_CODECVT
/// Attempt to set one of the acceptable unicode locales for conversion
CLI11_INLINE void set_unicode_locale() {
static const std::array<const char *, 3> unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}};
for(const auto &locale_name : unicode_locales) {
if(std::setlocale(LC_ALL, locale_name) != nullptr) {
return;
}
}
throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8");
}
template <typename F> struct scope_guard_t {
F closure;
explicit scope_guard_t(F closure_) : closure(closure_) {}
~scope_guard_t() { closure(); }
};
template <typename F> CLI11_NODISCARD CLI11_INLINE scope_guard_t<F> scope_guard(F &&closure) {
return scope_guard_t<F>{std::forward<F>(closure)};
}
#endif // !CLI11_HAS_CODECVT
CLI11_DIAGNOSTIC_PUSH
CLI11_DIAGNOSTIC_IGNORE_DEPRECATED
CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) {
#if CLI11_HAS_CODECVT
#ifdef _WIN32
return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(str, str + str_size);
#else
return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(str, str + str_size);
#endif // _WIN32
#else // CLI11_HAS_CODECVT
(void)str_size;
std::mbstate_t state = std::mbstate_t();
const wchar_t *it = str;
std::string old_locale = std::setlocale(LC_ALL, nullptr);
auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); });
set_unicode_locale();
std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state);
if(new_size == static_cast<std::size_t>(-1)) {
throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " +
std::to_string(it - str));
}
std::string result(new_size, '\0');
std::wcsrtombs(const_cast<char *>(result.data()), &str, new_size, &state);
return result;
#endif // CLI11_HAS_CODECVT
}
CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) {
#if CLI11_HAS_CODECVT
#ifdef _WIN32
return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(str, str + str_size);
#else
return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(str, str + str_size);
#endif // _WIN32
#else // CLI11_HAS_CODECVT
(void)str_size;
std::mbstate_t state = std::mbstate_t();
const char *it = str;
std::string old_locale = std::setlocale(LC_ALL, nullptr);
auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); });
set_unicode_locale();
std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state);
if(new_size == static_cast<std::size_t>(-1)) {
throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " +
std::to_string(it - str));
}
std::wstring result(new_size, L'\0');
std::mbsrtowcs(const_cast<wchar_t *>(result.data()), &str, new_size, &state);
return result;
#endif // CLI11_HAS_CODECVT
}
CLI11_DIAGNOSTIC_POP
} // namespace detail
CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); }
CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); }
// Flawfinder: ignore
CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); }
CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); }
CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); }
// Flawfinder: ignore
CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); }
#ifdef CLI11_CPP17
CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); }
CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); }
#endif // CLI11_CPP17
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
CLI11_INLINE std::filesystem::path to_path(std::string_view str) {
return std::filesystem::path{
#ifdef _WIN32
widen(str)
#else
str
#endif // _WIN32
};
}
#endif // CLI11_HAS_FILESYSTEM
// [CLI11:encoding_inl_hpp:end]
} // namespace CLI

View File

@ -0,0 +1,101 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// [CLI11:slim_windows_h_hpp:verbatim]
#ifdef _WIN32
// The most slimmed-down version of Windows.h.
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
// Enable components based on necessity.
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define OEMRESOURCE
#define NOATOM
#define NOCLIPBOARD
#define NOCOLOR
#define NOCTLMGR
#define NODRAWTEXT
#define NOGDI
#define NOKERNEL
#define NOUSER
#define NONLS
#define NOMB
#define NOMEMMGR
#define NOMETAFILE
#define NOMINMAX
#define NOMSG
#define NOOPENFILE
#define NOSCROLL
#define NOSERVICE
#define NOSOUND
#define NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOCOMM
#define NOKANJI
#define NOHELP
#define NOPROFILER
#define NODEFERWINDOWPOS
#define NOMCX
#include "Windows.h"
#undef WIN32_LEAN_AND_MEAN
#undef WIN32_EXTRA_LEAN
#undef NOGDICAPMASKS
#undef NOVIRTUALKEYCODES
#undef NOWINMESSAGES
#undef NOWINSTYLES
#undef NOSYSMETRICS
#undef NOMENUS
#undef NOICONS
#undef NOKEYSTATES
#undef NOSYSCOMMANDS
#undef NORASTEROPS
#undef NOSHOWWINDOW
#undef OEMRESOURCE
#undef NOATOM
#undef NOCLIPBOARD
#undef NOCOLOR
#undef NOCTLMGR
#undef NODRAWTEXT
#undef NOGDI
#undef NOKERNEL
#undef NOUSER
#undef NONLS
#undef NOMB
#undef NOMEMMGR
#undef NOMETAFILE
#undef NOMINMAX
#undef NOMSG
#undef NOOPENFILE
#undef NOSCROLL
#undef NOSERVICE
#undef NOSOUND
#undef NOTEXTMETRIC
#undef NOWH
#undef NOWINOFFSETS
#undef NOCOMM
#undef NOKANJI
#undef NOHELP
#undef NOPROFILER
#undef NODEFERWINDOWPOS
#undef NOMCX
#endif // _WIN32
// [CLI11:slim_windows_h_hpp:end]

View File

@ -8,6 +8,7 @@
#include <CLI/Validators.hpp> #include <CLI/Validators.hpp>
#include <CLI/Encoding.hpp>
#include <CLI/Macros.hpp> #include <CLI/Macros.hpp>
#include <CLI/StringTools.hpp> #include <CLI/StringTools.hpp>
#include <CLI/TypeTools.hpp> #include <CLI/TypeTools.hpp>
@ -127,7 +128,7 @@ namespace detail {
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
CLI11_INLINE path_type check_path(const char *file) noexcept { CLI11_INLINE path_type check_path(const char *file) noexcept {
std::error_code ec; std::error_code ec;
auto stat = std::filesystem::status(file, ec); auto stat = std::filesystem::status(to_path(file), ec);
if(ec) { if(ec) {
return path_type::nonexistent; return path_type::nonexistent;
} }

View File

@ -13,7 +13,9 @@ set(CLI11_headers
${CLI11_headerLoc}/StringTools.hpp ${CLI11_headerLoc}/StringTools.hpp
${CLI11_headerLoc}/TypeTools.hpp ${CLI11_headerLoc}/TypeTools.hpp
${CLI11_headerLoc}/Validators.hpp ${CLI11_headerLoc}/Validators.hpp
${CLI11_headerLoc}/Version.hpp) ${CLI11_headerLoc}/Version.hpp
${CLI11_headerLoc}/Encoding.hpp
${CLI11_headerLoc}/Argv.hpp)
set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl") set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl")
@ -24,7 +26,10 @@ set(CLI11_impl_headers
${CLI11_implLoc}/Option_inl.hpp ${CLI11_implLoc}/Option_inl.hpp
${CLI11_implLoc}/Split_inl.hpp ${CLI11_implLoc}/Split_inl.hpp
${CLI11_implLoc}/StringTools_inl.hpp ${CLI11_implLoc}/StringTools_inl.hpp
${CLI11_implLoc}/Validators_inl.hpp) ${CLI11_implLoc}/Validators_inl.hpp
${CLI11_implLoc}/Encoding_inl.hpp
${CLI11_implLoc}/Argv_inl.hpp
${CLI11_implLoc}/SlimWindowsH.hpp)
set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp) set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp)

View File

@ -5,7 +5,9 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
#include <CLI/impl/App_inl.hpp> #include <CLI/impl/App_inl.hpp>
#include <CLI/impl/Argv_inl.hpp>
#include <CLI/impl/Config_inl.hpp> #include <CLI/impl/Config_inl.hpp>
#include <CLI/impl/Encoding_inl.hpp>
#include <CLI/impl/Formatter_inl.hpp> #include <CLI/impl/Formatter_inl.hpp>
#include <CLI/impl/Option_inl.hpp> #include <CLI/impl/Option_inl.hpp>
#include <CLI/impl/Split_inl.hpp> #include <CLI/impl/Split_inl.hpp>

View File

@ -7,6 +7,7 @@
#include "app_helper.hpp" #include "app_helper.hpp"
#include <cmath> #include <cmath>
#include <array>
#include <complex> #include <complex>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
@ -261,6 +262,28 @@ TEST_CASE_METHOD(TApp, "OneString", "[app]") {
CHECK("mystring" == str); CHECK("mystring" == str);
} }
TEST_CASE_METHOD(TApp, "OneWideString", "[app]") {
std::wstring str;
app.add_option("-s,--string", str);
args = {"--string", "mystring"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK(L"mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringWideInput", "[app][unicode]") {
std::string str;
app.add_option("-s,--string", str);
std::array<const wchar_t *, 3> cmdline{{L"app", L"--string", L"mystring"}};
app.parse(static_cast<int>(cmdline.size()), cmdline.data());
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") { TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") {
std::string str; std::string str;
app.add_option("-s,--string", str); app.add_option("-s,--string", str);
@ -282,6 +305,16 @@ TEST_CASE_METHOD(TApp, "OneStringSingleStringInput", "[app]") {
CHECK("mystring" == str); CHECK("mystring" == str);
} }
TEST_CASE_METHOD(TApp, "OneStringSingleWideStringInput", "[app][unicode]") {
std::string str;
app.add_option("-s,--string", str);
app.parse(L"--string mystring");
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") { TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") {
std::string str; std::string str;
app.add_option("-s,--string", str); app.add_option("-s,--string", str);
@ -2463,3 +2496,21 @@ TEST_CASE("C20_compile", "simple") {
app.parse("--flag"); app.parse("--flag");
CHECK_FALSE(flag->empty()); CHECK_FALSE(flag->empty());
} }
// #14
TEST_CASE("System Args", "[app]") {
const char *commandline = CLI11_SYSTEM_ARGS_EXE " 1234 false \"hello world\"";
int retval = std::system(commandline);
if(retval == -1) {
FAIL("Executable '" << commandline << "' reported different argc count");
}
if(retval > 0) {
FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1));
}
if(retval != 0) {
FAIL("Executable '" << commandline << "' failed with an unknown return code");
}
}

View File

@ -49,7 +49,8 @@ set(CLI11_TESTS
StringParseTest StringParseTest
ComplexTypeTest ComplexTypeTest
TrueFalseTest TrueFalseTest
OptionGroupTest) OptionGroupTest
EncodingTest)
if(WIN32) if(WIN32)
list(APPEND CLI11_TESTS WindowsTest) list(APPEND CLI11_TESTS WindowsTest)
@ -89,6 +90,40 @@ else()
target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
endif() endif()
# Add special target that copies the data directory for tests
file(
GLOB_RECURSE DATA_FILES
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/data/*")
foreach(DATA_FILE IN LISTS DATA_FILES)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}"
"${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}"
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}"
VERBATIM)
target_sources(catch_main PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}")
endforeach()
# Build dependent applications which are launched from test code
set(CLI11_DEPENDENT_APPLICATIONS system_args)
foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS)
add_executable(${APP} applications/${APP}.cpp)
target_include_directories(${APP} PRIVATE ${CMAKE_SOURCE_DIR}/include)
add_dependencies(catch_main ${APP})
endforeach()
function(add_dependent_application_definitions TARGET)
foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS)
string(TOUPPER ${APP} APP_UPPERCASE)
target_compile_definitions(${TARGET}
PRIVATE CLI11_${APP_UPPERCASE}_EXE="$<TARGET_FILE:${APP}>")
endforeach()
endfunction()
# Target must already exist # Target must already exist
macro(add_catch_test TESTNAME) macro(add_catch_test TESTNAME)
target_link_libraries(${TESTNAME} PUBLIC catch_main) target_link_libraries(${TESTNAME} PUBLIC catch_main)
@ -108,6 +143,8 @@ foreach(T IN LISTS CLI11_TESTS)
set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA) set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA)
endif() endif()
add_executable(${T} ${T}.cpp) add_executable(${T} ${T}.cpp)
add_dependent_application_definitions(${T})
add_sanitizers(${T}) add_sanitizers(${T})
if(NOT CLI11_CUDA_TESTS) if(NOT CLI11_CUDA_TESTS)
target_link_libraries(${T} PRIVATE CLI11_warnings) target_link_libraries(${T} PRIVATE CLI11_warnings)
@ -117,6 +154,7 @@ foreach(T IN LISTS CLI11_TESTS)
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
add_executable(${T}_Single ${T}.cpp) add_executable(${T}_Single ${T}.cpp)
add_dependent_application_definitions(${T}_Single)
target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE) target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE)
add_catch_test(${T}_Single) add_catch_test(${T}_Single)
set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File") set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File")
@ -125,6 +163,7 @@ endforeach()
foreach(T IN LISTS CLI11_MULTIONLY_TESTS) foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
add_executable(${T} ${T}.cpp) add_executable(${T} ${T}.cpp)
add_dependent_application_definitions(${T})
add_sanitizers(${T}) add_sanitizers(${T})
target_link_libraries(${T} PUBLIC CLI11) target_link_libraries(${T} PUBLIC CLI11)
add_catch_test(${T}) add_catch_test(${T})

104
tests/EncodingTest.cpp Normal file
View File

@ -0,0 +1,104 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <array>
#include <string>
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem>
#endif // CLI11_HAS_FILESYSTEM
// "abcd"
static const std::string abcd_str = "abcd"; // NOLINT(runtime/string)
static const std::wstring abcd_wstr = L"abcd"; // NOLINT(runtime/string)
// "𓂀𓂀𓂀" - 4-byte utf8 characters
static const std::array<uint8_t, 12 + 1> egypt_utf8_codeunits{
{0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80}};
static const std::string egypt_str(reinterpret_cast<const char *>(egypt_utf8_codeunits.data()));
#ifdef _WIN32
static const std::array<uint16_t, 6 + 1> egypt_utf16_codeunits{{0xD80C, 0xDC80, 0xD80C, 0xDC80, 0xD80C, 0xDC80}};
static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf16_codeunits.data()));
#else
static const std::array<uint32_t, 3 + 1> egypt_utf32_codeunits{{0x00013080, 0x00013080, 0x00013080}};
static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf32_codeunits.data()));
#endif
// "Hello Halló Привет 你好 👩‍🚀❤️" - many languages and complex emojis
static const std::array<uint8_t, 50 + 1> hello_utf8_codeunits{
{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x48, 0x61, 0x6c, 0x6c, 0xc3, 0xb3, 0x20, 0xd0, 0x9f, 0xd1, 0x80,
0xd0, 0xb8, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0x20, 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd, 0x20, 0xf0,
0x9f, 0x91, 0xa9, 0xe2, 0x80, 0x8d, 0xf0, 0x9f, 0x9a, 0x80, 0xe2, 0x9d, 0xa4, 0xef, 0xb8, 0x8f}};
static const std::string hello_str(reinterpret_cast<const char *>(hello_utf8_codeunits.data()));
#ifdef _WIN32
static const std::array<uint16_t, 29 + 1> hello_utf16_codeunits{
{0x0048, 0x0065, 0x006c, 0x006c, 0x006f, 0x0020, 0x0048, 0x0061, 0x006c, 0x006c,
0x00f3, 0x0020, 0x041f, 0x0440, 0x0438, 0x0432, 0x0435, 0x0442, 0x0020, 0x4f60,
0x597d, 0x0020, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xde80, 0x2764, 0xfe0f}};
static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf16_codeunits.data()));
#else
static const std::array<uint32_t, 27 + 1> hello_utf32_codeunits{
{0x00000048, 0x00000065, 0x0000006c, 0x0000006c, 0x0000006f, 0x00000020, 0x00000048, 0x00000061, 0x0000006c,
0x0000006c, 0x000000f3, 0x00000020, 0x0000041f, 0x00000440, 0x00000438, 0x00000432, 0x00000435, 0x00000442,
0x00000020, 0x00004f60, 0x0000597d, 0x00000020, 0x0001f469, 0x0000200d, 0x0001f680, 0x00002764, 0x0000fe0f}};
static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf32_codeunits.data()));
#endif
// #14
TEST_CASE("Encoding: Widen", "[unicode]") {
using CLI::widen;
CHECK(abcd_wstr == widen(abcd_str));
CHECK(egypt_wstr == widen(egypt_str));
CHECK(hello_wstr == widen(hello_str));
CHECK(hello_wstr == widen(hello_str.c_str()));
CHECK(hello_wstr == widen(hello_str.c_str(), hello_str.size()));
#ifdef CLI11_CPP17
CHECK(hello_wstr == widen(std::string_view{hello_str}));
#endif // CLI11_CPP17
}
// #14
TEST_CASE("Encoding: Narrow", "[unicode]") {
using CLI::narrow;
CHECK(abcd_str == narrow(abcd_wstr));
CHECK(egypt_str == narrow(egypt_wstr));
CHECK(hello_str == narrow(hello_wstr));
CHECK(hello_str == narrow(hello_wstr.c_str()));
CHECK(hello_str == narrow(hello_wstr.c_str(), hello_wstr.size()));
#ifdef CLI11_CPP17
CHECK(hello_str == narrow(std::wstring_view{hello_wstr}));
#endif // CLI11_CPP17
}
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
// #14
TEST_CASE("Encoding: to_path roundtrip", "[unicode]") {
using std::filesystem::path;
#ifdef _WIN32
std::wstring native_str = CLI::widen(hello_str);
#else
std::string native_str = hello_str;
#endif // _WIN32
CHECK(CLI::to_path(hello_str).native() == native_str);
}
#endif // CLI11_HAS_FILESYSTEM

View File

@ -13,6 +13,9 @@
#endif #endif
#include "catch.hpp" #include "catch.hpp"
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <utility> #include <utility>
@ -67,3 +70,54 @@ inline void unset_env(std::string name) {
unsetenv(name.c_str()); unsetenv(name.c_str());
#endif #endif
} }
CLI11_INLINE void check_identical_files(const char *path1, const char *path2) {
std::string err1 = CLI::ExistingFile(path1);
if(!err1.empty()) {
FAIL("Could not open " << path1 << ": " << err1);
}
std::string err2 = CLI::ExistingFile(path2);
if(!err2.empty()) {
FAIL("Could not open " << path2 << ": " << err2);
}
// open files at the end to compare size first
std::ifstream file1(path1, std::ifstream::ate | std::ifstream::binary);
std::ifstream file2(path2, std::ifstream::ate | std::ifstream::binary);
if(!file1.good()) {
FAIL("File " << path1 << " is corrupted");
}
if(!file2.good()) {
FAIL("File " << path2 << " is corrupted");
}
if(file1.tellg() != file2.tellg()) {
FAIL("Different file sizes:\n " << file1.tellg() << " bytes in " << path1 << "\n " << file2.tellg()
<< " bytes in " << path2);
}
// rewind files
file1.seekg(0);
file2.seekg(0);
std::array<uint8_t, 10240> buffer1;
std::array<uint8_t, 10240> buffer2;
for(size_t ibuffer = 0; file1.good(); ++ibuffer) {
// Flawfinder: ignore
file1.read(reinterpret_cast<char *>(buffer1.data()), static_cast<std::streamsize>(buffer1.size()));
// Flawfinder: ignore
file2.read(reinterpret_cast<char *>(buffer2.data()), static_cast<std::streamsize>(buffer2.size()));
for(size_t i = 0; i < static_cast<size_t>(file1.gcount()); ++i) {
if(buffer1[i] != buffer2[i]) {
FAIL(std::hex << std::setfill('0') << "Different bytes at position " << (ibuffer * 10240 + i) << ":\n "
<< "0x" << std::setw(2) << static_cast<int>(buffer1[i]) << " in " << path1 << "\n "
<< "0x" << std::setw(2) << static_cast<int>(buffer2[i]) << " in " << path2);
}
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include <CLI/CLI.hpp>
#include <cstring>
int main(int argc, char **argv) {
if(argc != CLI::argc()) {
return -1;
}
for(int i = 0; i < argc; i++) {
if(std::strcmp(argv[i], CLI::argv()[i]) != 0) {
return i + 1;
}
}
return 0;
}

1
tests/data/unicode.txt Normal file
View File

@ -0,0 +1 @@
Hello Halló Привет 你好 👩‍🚀❤️

View File

@ -57,6 +57,20 @@ testnames = [
['link_test_2', {'link_with': link_test_lib}], ['link_test_2', {'link_with': link_test_lib}],
] ]
dependent_applications = [
'system_args'
]
dependent_applications_definitions = []
#dependent_applications_targets = []
foreach app: dependent_applications
app_target = executable(app, 'applications'/app + '.cpp',
build_by_default: false
)
#dependent_applications_targets += dependency(app_target)
dependent_applications_definitions += '-DCLI11_@0@_EXE="@1@"'.format(app.to_upper(), app_target)
endforeach
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
testnames += [['WindowsTest', {}]] testnames += [['WindowsTest', {}]]
endif endif
@ -69,7 +83,7 @@ foreach n: testnames
name = n[0] name = n[0]
kwargs = n[1] kwargs = n[1]
t = executable(name, name + '.cpp', t = executable(name, name + '.cpp',
cpp_args: kwargs.get('cpp_args', []), cpp_args: kwargs.get('cpp_args', []) + dependent_applications_definitions,
build_by_default: false, build_by_default: false,
dependencies: [testdep] + kwargs.get('dependencies', []), dependencies: [testdep] + kwargs.get('dependencies', []),
link_with: kwargs.get('link_with', []) link_with: kwargs.get('link_with', [])