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
run: meson compile -C build-meson
cmake-config:
name: CMake config check
runs-on: ubuntu-20.04
cmake-config-ubuntu-1804:
name: CMake config check (Ubuntu 18.04)
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
@ -175,6 +175,12 @@ jobs:
cmake-version: "3.10"
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)
uses: ./.github/actions/quick_cmake
with:
@ -212,6 +218,12 @@ jobs:
cmake-version: "3.16"
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
uses: ./.github/actions/quick_cmake
with:

2
.gitignore vendored
View File

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

View File

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

View File

@ -4,6 +4,7 @@ linelength=120 # As in .clang-format
# 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/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/check # Catch uses CHECK(a == b) (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)
- [Subclassing](#subclassing)
- [How it works](#how-it-works)
- [Unicode support](#unicode-support)
- [Utilities](#utilities)
- [Other libraries](#other-libraries)
- [API](#api)
@ -164,9 +165,6 @@ this library:
option to disable it).
- Autocomplete: This might eventually be added to both Plumbum and CLI11, but it
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
@ -278,13 +276,13 @@ To set up, add options, and run, your main function will look something like
this:
```cpp
int main(int argc, char** argv) {
int main() {
CLI::App app{"App description"};
std::string filename = "default";
app.add_option("-f,--file", filename, "A help string");
CLI11_PARSE(app, argc, argv);
CLI11_PARSE(app);
return 0;
}
```
@ -293,7 +291,7 @@ int main(int argc, char** argv) {
```cpp
try {
app.parse(argc, argv);
app.parse();
} catch (const CLI::ParseError &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
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>
</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
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]
#ifndef CLI11_PARSE
#define CLI11_PARSE(app, argc, argv) \
#define CLI11_PARSE(app, ...) \
try { \
(app).parse((argc), (argv)); \
(app).parse(__VA_ARGS__); \
} catch(const CLI::ParseError &e) { \
return (app).exit(e); \
}
@ -837,15 +837,25 @@ class App {
/// Reset the parsed data
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.
/// 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 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.
/// 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
/// process
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.
/// 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 "Encoding.hpp"
#include "Argv.hpp"
#include "StringTools.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
/** <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 **/
#ifdef CLI11_COMPILE
#define CLI11_INLINE

View File

@ -18,6 +18,7 @@
#include <vector>
// [CLI11:public_includes:end]
#include "Encoding.hpp"
#include "StringTools.hpp"
namespace CLI {
@ -242,8 +243,10 @@ struct is_mutable_container<
decltype(std::declval<T>().clear()),
decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
std::declval<const typename T::value_type &>()))>,
void>>
: public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {};
void>> : public conditional_t<std::is_constructible<T, std::string>::value ||
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)
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_assignable = 23,
string_constructible = 24,
wstring_assignable = 25,
wstring_constructible = 26,
other = 45,
// special wrapper or container types
wrapper_value = 50,
@ -613,6 +618,27 @@ struct classify_object<
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
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};
@ -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,
/// vectors, and enumerations
template <typename T> struct uncommon_type {
using type = typename std::conditional<!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 && !is_complex<T>::value &&
!is_mutable_container<T>::value && !std::is_enum<T>::value,
std::true_type,
std::false_type>::type;
using type = typename std::conditional<
!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 && !std::is_constructible<T, std::wstring>::value &&
!is_complex<T>::value && !is_mutable_container<T>::value && !std::is_enum<T>::value,
std::true_type,
std::false_type>::type;
static constexpr bool value = type::value;
};
@ -1005,6 +1032,23 @@ bool lexical_cast(const std::string &input, T &output) {
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
template <typename T,
enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
@ -1133,7 +1177,9 @@ template <typename AssignTo,
typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
(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>
bool lexical_assign(const std::string &input, AssignTo &output) {
return lexical_cast(input, output);
@ -1144,7 +1190,9 @@ template <typename AssignTo,
typename ConvertTo,
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_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>
bool lexical_assign(const std::string &input, AssignTo &output) {
if(input.empty()) {

View File

@ -26,34 +26,6 @@
// [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
#include <filesystem> // NOLINT(build/include)
#else

View File

@ -9,6 +9,9 @@
// This include is only needed for IDEs to discover symbols
#include <CLI/App.hpp>
#include <CLI/Argv.hpp>
#include <CLI/Encoding.hpp>
// [CLI11:public_includes:set]
#include <algorithm>
#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(name_.empty() || has_automatic_name_) {
has_automatic_name_ = true;
name_ = argv[0];
name_ = detail::maybe_narrow(argv[0]);
}
std::vector<std::string> args;
args.reserve(static_cast<std::size_t>(argc) - 1U);
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));
}
@ -515,6 +532,10 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included
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) {
// Clear if parsed
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/Encoding.hpp>
#include <CLI/Macros.hpp>
#include <CLI/StringTools.hpp>
#include <CLI/TypeTools.hpp>
@ -127,7 +128,7 @@ namespace detail {
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
CLI11_INLINE path_type check_path(const char *file) noexcept {
std::error_code ec;
auto stat = std::filesystem::status(file, ec);
auto stat = std::filesystem::status(to_path(file), ec);
if(ec) {
return path_type::nonexistent;
}

View File

@ -13,7 +13,9 @@ set(CLI11_headers
${CLI11_headerLoc}/StringTools.hpp
${CLI11_headerLoc}/TypeTools.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")
@ -24,7 +26,10 @@ set(CLI11_impl_headers
${CLI11_implLoc}/Option_inl.hpp
${CLI11_implLoc}/Split_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)

View File

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

View File

@ -7,6 +7,7 @@
#include "app_helper.hpp"
#include <cmath>
#include <array>
#include <complex>
#include <cstdint>
#include <cstdlib>
@ -261,6 +262,28 @@ TEST_CASE_METHOD(TApp, "OneString", "[app]") {
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]") {
std::string str;
app.add_option("-s,--string", str);
@ -282,6 +305,16 @@ TEST_CASE_METHOD(TApp, "OneStringSingleStringInput", "[app]") {
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]") {
std::string str;
app.add_option("-s,--string", str);
@ -2463,3 +2496,21 @@ TEST_CASE("C20_compile", "simple") {
app.parse("--flag");
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
ComplexTypeTest
TrueFalseTest
OptionGroupTest)
OptionGroupTest
EncodingTest)
if(WIN32)
list(APPEND CLI11_TESTS WindowsTest)
@ -89,6 +90,40 @@ else()
target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
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
macro(add_catch_test TESTNAME)
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)
endif()
add_executable(${T} ${T}.cpp)
add_dependent_application_definitions(${T})
add_sanitizers(${T})
if(NOT CLI11_CUDA_TESTS)
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)
add_executable(${T}_Single ${T}.cpp)
add_dependent_application_definitions(${T}_Single)
target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE)
add_catch_test(${T}_Single)
set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File")
@ -125,6 +163,7 @@ endforeach()
foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
add_executable(${T} ${T}.cpp)
add_dependent_application_definitions(${T})
add_sanitizers(${T})
target_link_libraries(${T} PUBLIC CLI11)
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
#include "catch.hpp"
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
@ -67,3 +70,54 @@ inline void unset_env(std::string name) {
unsetenv(name.c_str());
#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}],
]
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'
testnames += [['WindowsTest', {}]]
endif
@ -69,7 +83,7 @@ foreach n: testnames
name = n[0]
kwargs = n[1]
t = executable(name, name + '.cpp',
cpp_args: kwargs.get('cpp_args', []),
cpp_args: kwargs.get('cpp_args', []) + dependent_applications_definitions,
build_by_default: false,
dependencies: [testdep] + kwargs.get('dependencies', []),
link_with: kwargs.get('link_with', [])